diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5ee90bc..5f27ac8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,128 +1,18 @@ -# ============================================================================= -# Pre Defined Variables -# ============================================================================= -# User provided Go version of Docker image -ARG VARIANT -# Default Go version. Choices: 1, 1.15, 1.14 -ARG VARIANT_DEFAULT="1" -# Default Language -ARG LC_ALL_DEFAULT='en_US.utf8' -ARG LANG_DEFAULT='en_US.utf8' -# Default Node.js settings -ARG INSTALL_NODE_DEFAULT="false" -ARG NODE_VERSION_DEFAULT="lts/*" - -# ============================================================================= -# Define Dockerfile. -# -# For image contents see: -# https://github.com/microsoft/vscode-dev-containers/tree/v0.145.1/containers/go/.devcontainer/base.Dockerfile -# ============================================================================= -FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT:-$VARIANT_DEFAULT} - -# Declare user args to receive while building an image. -ARG LANG -ARG LC_ALL -ARG INSTALL_NODE -ARG NODE_VERSION - -ENV \ - LANG="${LANG:-$LANG_DEFAULT}" \ - LC_ALL="${LC_ALL:-$LC_ALL_DEFAULT}" \ - PATH="/usr/local/go/bin:${PATH}" \ - # Enforce go module mode - GO111MODULE='on' \ - # Fix: https://github.com/microsoft/vscode-dev-containers/issues/51 - SHELL="/bin/bash" - -#RUN localedef -f UTF-8 - -# [Option] Install Node.js -ARG INSTALL_NODE="${INSTALL_NODE:-INSTALL_NODE_DEFAULT}" -ARG NODE_VERSION="${NODE_VERSION:-NODE_VERSION_DEFAULT}" -RUN if [ "${INSTALL_NODE}" = "true" ]; then \ - echo 'Installing Node.js'; \ - su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; \ - fi - -# Bash script file to run right after the container was created. -# See: "postCreateCommand" section in "devcontainer.json" and "postCreateCommand.sh" -# file. -COPY postCreateCommand.sh /postCreateCommand.sh - -# Install additional OS packages. -RUN export DEBIAN_FRONTEND=noninteractive \ - && apt-get update \ - && apt-get -y upgrade \ - && apt-get -y install --no-install-recommends \ - # The `rg` is a string search command from files. ex) rg ./cmd foo - ripgrep \ - # Directory tree viewer for documentation. - tree \ - # xz to untar xz compressed files - xz-utils \ - # missing locales - locales - -# ShellCheck - Static analyzer and formatter for shell script -# Note: Install the latest shellcheck. See: https://github.com/koalaman/shellcheck/issues/704 -RUN url_download="https://github.com/koalaman/shellcheck/releases/download/latest/shellcheck-latest.linux.$(uname -m).tar.xz" \ - && path_tmp_dir="$(pwd)/tmp_install_dir" \ - && wget -P "${path_tmp_dir}/" "$url_download"\ - && tar xvf ${path_tmp_dir}/shellcheck* -C "${path_tmp_dir}/" \ - && cp "${path_tmp_dir}/shellcheck-latest/shellcheck" "$(dirname $(which tree))/shellcheck" \ - # Smoke test - && shellcheck --version \ - && rm -r "$path_tmp_dir" - -# golangci-lint - The fast Go linters runner. Version=latest -# binary will be installed under: $(go env GOPATH)/bin/golangci-lint -RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin \ - # Smoke test - && golangci-lint --version - -# Install anything else needed to go get -RUN go get -u -v \ - # gopls (language server) - # `gopls` will install the packages in the URL below as well. - # https://github.com/golang/vscode-go/blob/master/docs/tools.md#table-of-contents - "golang.org/x/tools/gopls@latest" \ - # go-carpet (Cover area checker) - "github.com/msoap/go-carpet" \ - # shfmt (shellformat) is a shell script linter - "mvdan.cc/sh/v3/cmd/shfmt" \ - # gp enables to share go files over Go Playground - "github.com/tenntenn/goplayground/cmd/gp" \ - # gomarkdoc generates documentation in markdown - "github.com/princjef/gomarkdoc/cmd/gomarkdoc" \ - #"github.com/rogpeppe/godef" \ - # ------------------------------------------------------------------------- - # irregular install (Download source and build) - # ------------------------------------------------------------------------- - && cd "${GOPATH}/pkg/mod" \ - && go get -v -d \ - # gocode and gocode-modules is no-good to install as module - "github.com/stamblerre/gocode" \ - # cobra (sub-command creation) - "github.com/spf13/cobra" \ - # gomock - "github.com/golang/mock/gomock" \ - # gocode and gocode-modules - && cd ${GOPATH}/pkg/mod/github.com/stamblerre/gocode* \ - && go build -o "${GOPATH}/bin/gocode-gomod" . \ - # cobra command - && cd ${GOPATH}/pkg/mod/github.com/spf13/cobra*/cobra \ - && go build -o "${GOPATH}/bin/cobra" . \ - # mockgen - && cd ${GOPATH}/pkg/mod/github.com/golang/mock*/mockgen \ - && go install \ - # List bin files - && echo "- List dir: ${GOPATH}/bin" && ls -l "${GOPATH}/bin" - -# Miscellaneous -RUN \ - # Set path for go - echo 'export PATH="/go/bin:/usr/local/go/bin:${PATH}"' >> "${HOME}/.bashrc" - -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 +# [Choice] https://github.com/KEINOS/VSCode-Dev-Container-Go/pkgs/container/vscode-dev-container-go +ARG VARIANT="latest" + +# ----------------------------------------------------------------------------- +# Main Stage +# ----------------------------------------------------------------------------- +# ghcr.io/keinos/vscode-dev-container-go is an Alpine-based container image which +# provides a VSCode development environment over Docker. This image is weekly +# scanned for vulnerability and security with Synk, Dockle and Trivy. For detals +# see: ./README.md +FROM ghcr.io/keinos/vscode-dev-container-go:${VARIANT} + +# [Optional] Uncomment this section to install additional OS packages. +# USER root +# RUN apk add --no-cache + +USER vscode +RUN cd /tmp && go install golang.org/x/lint/golint@latest \ No newline at end of file diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 7e8d9ff..fb799b8 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -3,24 +3,20 @@ This directory is for [GitHub Codespaces](https://github.com/features/codespaces) and/or [VS Code + Docker](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) users for development. -It includes most of the necessary packages and tools for developing Golang app. Aiming to provide the same environment to develop the app. +It includes: + +- Latest [Alpine-based container image](https://github.com/KEINOS/VSCode-Dev-Container-Go/pkgs/container/vscode-dev-container-go) with vulnerability and security scan via Synk, Dockle and Trivy. +- Latest Go version. +- Common packages and tools for developing Golang app and shell scripts. + - [Installed tools and Go modules](https://github.com/KEINOS/VSCode-Dev-Container-Go/blob/main/image_info.txt) | VSCode-Dev-Container-Go | KEINOS @ GitHub +- Weekly updated. ## Developing Online -If GitHub detects this directory (`.devcontainer`) in the repo, then you will be able to develop online via [GitHub Codespaces](https://github.com/features/codespaces). +Just press the `.`(dot) key on GitHub and you should be redirected to [GitHub Codespaces](https://github.com/features/codespaces). (You may need to register to use Codesspaces) ## VS Code + Docker User The container contains VS Code Server as well. If you already have installed the "[Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)" extension, then press "F1" and select "`Remote-Containers: Open in Container`". After a while, you'll get most of the environment needed to develop and debug. - -## File Description - -- [cobra.yaml](cobra.yaml): Default `cobra` command Settings. Used for `$ cobra add ..` -- [devcontainer.env](devcontainer.env): ENV variables to be loaded once when the container's created. -- [devcontainer.json](devcontainer.json): VSCode Extensions to be installed and env settings. -- [Dockerfile](Dockerfile): Debian 10 (buster) based Golang development container. -- [postCreateCommand.sh](postCreateCommand.sh): Initialization script that runs after the container and the VSCode server is up. -- [README.md](README.md): This file. ;-) -- [welcome.sh](welcome.sh): Bash script to display the basic info and TIPs to use in the first shell login. diff --git a/.devcontainer/devcontainer.env b/.devcontainer/devcontainer.env deleted file mode 100644 index 360215d..0000000 --- a/.devcontainer/devcontainer.env +++ /dev/null @@ -1,26 +0,0 @@ -# ============================================================================= -# devcontainer.env defines ENV variables when run. -# -# Same as: `docker run --env-file $(pwd)/devcontainer.env` -# Note that you need to rebuild the container if you change the values. -# ============================================================================= - -# ----------------------------------------------------------------------------- -# Host's Env Variables -# ----------------------------------------------------------------------------- -# Language detection -# - Sample: Japanese -# LANG=ja_JP.UTF-8 -# LC_ALL=ja_JP.UTF-8 -# - Sample: Spanish -# LANG=es_MX.UTF-8 -# LC_ALL=es_MX.UTF-8 -# - Sample: Use local locale -# LANG -# LC_ALL -LANG -LC_ALL - -# ----------------------------------------------------------------------------- -# User Defined Variables. -# ----------------------------------------------------------------------------- diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ced7a97..382a7c2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,49 +1,48 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/go { - "name": "Go", + "name": "Go Dev", "build": { "dockerfile": "Dockerfile", "args": { - // Go version: 1, 1.15, 1.14 - "VARIANT": "1.15", - // Options - "INSTALL_NODE": "false", - "NODE_VERSION": "lts/*" + // Choices: https://github.com/KEINOS/VSCode-Dev-Container-Go/pkgs/container/vscode-dev-container-go + "VARIANT": "latest" } }, - "runArgs": [ - "--cap-add=SYS_PTRACE", - "--security-opt", "seccomp=unconfined", - "--env-file",".devcontainer/devcontainer.env" - ], - // Set VSCode settings + "runArgs": [ + "--rm", + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined" + ], + + // Set *default* container specific settings.json values on container create. "settings": { + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": true, "go.gopath": "/go", "go.goroot": "/usr/local/go", - "go.lintFlags": ["--enable-all", "--new"], - "go.toolsGopath": "/go/bin", - "go.toolsManagement.checkForUpdates": "proxy", - "go.useLanguageServer": true, - "terminal.integrated.shell.linux": "/bin/bash", - "terminal.integrated.shellArgs.linux": ["-l"] + "terminal.integrated.profiles.linux": { + "bash (login)": { + "path": "/bin/bash", + "args": [] + } + }, + "shellformat.path": "/go/bin/shfmt" }, - // VSCode extension ID to be installed + // Add the IDs of extensions you want installed when the container is created. "extensions": [ "golang.Go", - "editorconfig.editorconfig", - "foxundermoon.shell-format", - "ms-ceintl.vscode-language-pack-ja", - "ms-ceintl.vscode-language-pack-es", - "ms-azuretools.vscode-docker", - "ms-vsonline.vsonline", - "github.github-vscode-theme", - "github.vscode-pull-request-github", - "davidanson.vscode-markdownlint" + "foxundermoon.shell-format" ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "/bin/bash /postCreateCommand.sh;" + "postCreateCommand": "go mod download", - // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. - //"remoteUser": "vscode" -} + // Comment out to connect as root to debug container. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh deleted file mode 100644 index 19bed57..0000000 --- a/.devcontainer/postCreateCommand.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Place any script here which you want to run after creating the container. -echo '===============================================================================' -echo ' Post Create Command' -echo '===============================================================================' - -set -eu - -# Sim-link Welcome message for bash -ln -s "$(pwd)/.devcontainer/welcome.sh" "${HOME}/.welcome.sh" - -# Single quotes are intensional. So, not to expand expressions. -# shellcheck disable=SC2016 -echo '"${HOME}/.welcome.sh"' >>"${HOME}/.bashrc" - -# Sim-llink Cobra configuration file to home -ln -s "$(pwd)/.devcontainer/cobra.yaml" "${HOME}/.cobra.yaml" - -# Make sure go.mod matches the source code in the module. -go mod tidy - -# Set language -LANGUAGE="${LANG//\./:}" # <-- echo $LANG | sed "s/\./:/" -echo "export LANGUAGE=${LANGUAGE}" >>"${HOME}/.bashrc" - -echo "${LC_ALL} UTF-8" >>/etc/locale.gen -locale-gen "$LC_ALL" -update-locale LANG="$LANG" diff --git a/.devcontainer/welcome.sh b/.devcontainer/welcome.sh deleted file mode 100755 index 465a3eb..0000000 --- a/.devcontainer/welcome.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -# Remove go-build* files in temp -rm -rf /tmp/go-build* 2>/dev/null 1>/dev/null - -echo "- OS: $(head -n1 /dev/null 2>/dev/null; then flag_covered_all=$FALSE echo 'NG' - echo >&2 " - ABOUT : ${msg_error}" - echo >&2 " - DETAILS: ${url_reference}" + echo " - ABOUT : ${msg_error}" + echo " - DETAILS: ${url_reference}" + return $FALSE fi echo 'OK' @@ -40,8 +41,7 @@ echo 'Checking requirements for:' echo echo 'Shell scripts:' -isAvailable \ - shellcheck \ +isAvailable shellcheck \ '"shellcheck" is a static analysis tool for shell scripts.' \ 'https://github.com/koalaman/shellcheck' @@ -51,24 +51,21 @@ isAvailable shfmt \ echo echo 'Go programs:' -isAvailable \ - go \ +isAvailable go \ '"go" is required as a matter of course.' \ 'https://golang.org/' -isAvailable \ - gofmt \ +isAvailable gofmt \ '"gofmt" is a formatter for golang.' \ 'https://golang.org/cmd/gofmt/' -isAvailable \ - golangci-lint \ +isAvailable golangci-lint \ '"golangci-lint" is is a Go linters aggregator.' \ 'https://golangci-lint.run/' if [ $flag_covered_all -ne 0 ]; then echo - echo >&2 "Some requirements missing." + echo >&2 "ERROR: Some requirements were missing." exit $FAILURE fi diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml new file mode 100644 index 0000000..67a9381 --- /dev/null +++ b/.github/docker-compose.yml @@ -0,0 +1,74 @@ +# ============================================================================= +# Docker Compose file for testing on Go versions 1.14~1.17 and latest. +# ============================================================================= +# It is recommended to run specifying a specific Go version and not at once. +# +# Since the service `tidy` will update/re-write the "go.mod" file to the latest +# version, during it's process the "go.mod" file will be gone temporarily. Thus, +# it will cause failure in the other containers becaue of missing "go.mod" file. +# +# Recommended usage: +# docker-compose --file ./.github/docker-compose.yml up tidy && \ +# docker-compose --file ./.github/docker-compose.yml up 1.15 +# +# Not recommended but to run all tests at once: +# docker-compose --file ./.github/docker-compose.yml up +version: "3.9" +services: + # Service tidy updates the module versions in go.mod to the latest. + tidy: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: 1.15-alpine + volumes: + - ..:/workspaces + entrypoint: [ "./.github/update-go-mod.sh" ] + # Service coverage runs the tests with coverage and fails if coverage is below + # 100%. + coverage: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: alpine + volumes: + - ..:/workspaces + entrypoint: [ "./.github/run-tests-coverage.sh" ] + # Service v1_15 runs the tests on Go v1.15. + v1_15: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: 1.15-alpine + volumes: + - ..:/workspaces + # Service v1_16 runs the tests on Go v1.16. + v1_16: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: 1.16-alpine + volumes: + - ..:/workspaces + # Service v1_17 runs the tests on Go v1.17. + v1_17: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: 1.17-alpine + volumes: + - ..:/workspaces + # Service latest runs the tests on latest Go docker image. + latest: + build: + context: .. + dockerfile: ./.github/Dockerfile + args: + VARIANT: alpine + volumes: + - ..:/workspaces \ No newline at end of file diff --git a/.github/mergify.yml b/.github/mergify.yml index 9abc51e..aee689a 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -8,14 +8,34 @@ pull_request_rules: actions: merge: method: squash - strict: smart+fasttrack - name: automatic merge on CI success if only markdown and/or Golang files were changed conditions: - "check-success=merge_tests" - files~=.\.(?i)(md|go)$ - base=main + - check-success=lint + - check-success=Analyze (go) + - check-success=Run tests on Go via container + - check-success=Platform test (ubuntu-latest) + - check-success=Platform test (macos-latest) + - check-success=Platform test (windows-latest) - -draft actions: merge: method: squash - strict: smart+fasttrack + - name: Automatic merge on approval + conditions: + - author=KEINOS + - base=main + - title~=^Changes + - files=go.mod + - files=go.sum + - "#files=2" + - check-success=Analyze (go) + - check-success=Run tests on Go via container + - check-success=Platform test (ubuntu-latest) + - check-success=Platform test (macos-latest) + - check-success=Platform test (windows-latest) + actions: + merge: + method: merge \ No newline at end of file diff --git a/.github/run-tests-coverage.sh b/.github/run-tests-coverage.sh index d726600..fc2c166 100755 --- a/.github/run-tests-coverage.sh +++ b/.github/run-tests-coverage.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # ============================================================================= # Test Script to Run Go Test and Check 100% Coverage # ============================================================================= @@ -8,30 +8,46 @@ # Requirements: # - go-carpet: https://github.com/msoap/go-carpet +set -eu +set -o pipefail + # ----------------------------------------------------------------------------- # Constants # ----------------------------------------------------------------------------- -PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")" +PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${0:?'source missing'}")" && pwd)")" SUCCESS=0 FAILURE=1 TRUE=0 FALSE=1 +# Check if go-carpet is installed +if ! which go-carpet 1>/dev/null 2>/dev/null; then + PATH_DIR_RETURN="$(pwd)" + + cd /tmp && { + echo '* Installing go-carpet ...' + go install "github.com/msoap/go-carpet@latest" + } + + cd "$PATH_DIR_RETURN" +fi + # ----------------------------------------------------------------------------- # Functions # ----------------------------------------------------------------------------- # indentStdIn indents the STDIN given to the function -function indentStdIn() { +indentStdIn() { indent="\t" - while read -r line; do + while IFS= read -r line; do echo -e "${indent}${line}" done + echo } # isModeVerbose just returns a bool whether it's in verbose # mode or not. -function isModeVerbose() { +isModeVerbose() { if [ "$mode_verbose" -eq 0 ]; then return $TRUE fi @@ -40,7 +56,7 @@ function isModeVerbose() { } # runGoCarpet displays details of the coverage -function runGoCarpet() { +runGoCarpet() { if ! which go-carpet >/dev/null; then echo '* aborted' echo >&2 ' * Command "go-carpet" not found.' @@ -54,7 +70,7 @@ function runGoCarpet() { } # runGoVet runs Go vet for static analysis. -function runGoVet() { +runGoVet() { description="${1:?'Test description missing.'}" path_dir="${2:?'Path is missing'}" @@ -73,6 +89,7 @@ function runGoVet() { exit $FAILURE fi echo ' Success! All Go vet static analysis passed.' + return $SUCCESS } @@ -80,7 +97,7 @@ function runGoVet() { # If verbose option is provided then it will display the details. If the # coverage was lower than 100% then it will fail and show the cover area # as well. -function runTests() { +runTests() { description="${1:?'Test description missing.'}" path_dir="${2:?'Path is missing'}" name_file_coverage='coverage.out' @@ -107,20 +124,22 @@ function runTests() { if [ "$coverage" = "100.0%" ]; then echo ' Success! Coverage: 100%' + return $SUCCESS + fi + + # Displays where to cover, if the total coverage wasn't 100% + if isModeVerbose; then + echo '- Cover area' + runGoCarpet | indentStdIn + echo >&2 " ERROR: Coverage failed. Did not cover 100% of the statements." else - # Displays where to cover, if the total coverage wasn't 100% - if isModeVerbose; then - echo '- Cover area' - runGoCarpet | indentStdIn - echo >&2 " ERROR: Coverage failed. Did not cover 100% of the statements." - else - echo >&2 " ERROR: Coverage failed. Did not cover 100% of the statements." - echo >&2 " Use '--verbose' option to see where to cover." - fi - echo >&2 " Coverage: ${coverage}" - exit $FAILURE + echo >&2 " ERROR: Coverage failed. Did not cover 100% of the statements." + echo >&2 " Use '--verbose' option to see where to cover." fi + echo >&2 " Coverage: ${coverage}" + + return $FAILURE } # ----------------------------------------------------------------------------- @@ -135,8 +154,6 @@ echo "${@}" | grep -e "-v" -e "--verbose" >/dev/null && { # ----------------------------------------------------------------------------- # Main # ----------------------------------------------------------------------------- -set -eu -set -o pipefail if isModeVerbose; then echo '* Running in verbose mode.' diff --git a/.github/run-tests-lint.sh b/.github/run-tests-lint.sh index 09c514b..2a4ee3c 100755 --- a/.github/run-tests-lint.sh +++ b/.github/run-tests-lint.sh @@ -10,7 +10,7 @@ # Constants # ----------------------------------------------------------------------------- PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")" -PATH_FILE_CONF_GOLANGCILINT="${PATH_DIR_PARENT}/.github/golangci.yml" +PATH_FILE_CONF_GOLANGCILINT="${PATH_DIR_PARENT}/.golangci.yml" SUCCESS=0 FAILURE=1 diff --git a/.github/update-go-mod.sh b/.github/update-go-mod.sh new file mode 100755 index 0000000..da5fdbb --- /dev/null +++ b/.github/update-go-mod.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# ============================================================================= +# This script updates Go modules to the latest version. +# ============================================================================= +# It will remove the go.mod file and run `go mod tidy` to get the latest moule +# versions. +# Then it will run the tests to make sure the code is still working, and fails +# if any errors are found during the process. +# +# NOTE: This script is aimed to run in the container via docker-compose. +# See "tidy" service: ./docker-compose.yml +# ============================================================================= + +set -eu + +echo '* Backup modules ...' +mv go.mod go.mod.bak +mv go.sum go.sum.bak + +echo '* Create new blank go.mod ...' +head go.mod + +echo '* Run go tidy ...' +go mod tidy + +echo '* Run tests ...' +go test ./... && { + echo '* Testing passed. Removing old go.mod file ...' + rm -f go.mod.bak + rm -f go.sum.bak + echo 'Successfully updated modules!' +} diff --git a/.github/workflows/codeQL-analysis.yml b/.github/workflows/codeQL-analysis.yml new file mode 100644 index 0000000..9b53f45 --- /dev/null +++ b/.github/workflows/codeQL-analysis.yml @@ -0,0 +1,64 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +name: "CodeQL" + +on: + push: + schedule: + # Runs at 16:30 UTC on day-of-month 1 (Every day-of-month 1 at AM 01:30 JST, my time) + # See: https://crontab.guru/ + - cron: '30 16 1 * *' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 \ No newline at end of file diff --git a/.github/workflows/coverage-tests.yaml b/.github/workflows/coverage-tests.yaml index a043b49..45aaffb 100644 --- a/.github/workflows/coverage-tests.yaml +++ b/.github/workflows/coverage-tests.yaml @@ -1,49 +1,62 @@ +# Workflow to check code coverage on push. +# This action caches the built Docker image for a month unless any change in the Dockerfile. name: 100% Coverage on: workflow_dispatch: - pull_request: + push: -jobs: - coverage: - name: Unit test and coverage - - strategy: - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - - runs-on: ${{ matrix.platform }} +env: + PATH_CACHE: /tmp/docker-img-arch +jobs: + go: + name: Run tests on Go via container + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout repo + uses: actions/checkout@v2 - - uses: actions/setup-go@v2 - with: - go-version: 1.15.x - - - name: Use Cache + - name: Create image tag + id: imagetag + run: | + HASH_IMAGE=${{ hashFiles('./.github/Dockerfile') }} + VARIANT=$(TZ=UTC-9 date '+%Y%m') + TAG="${HASH_IMAGE:0:7}:${VARIANT}" + PATH_TAR=${{ env.PATH_CACHE }}"/tar" + echo "::set-output name=TAG::${TAG}" + echo "::set-output name=PATH_TAR::${PATH_TAR}" + + - name: Cache or restore image archive + id: cache uses: actions/cache@v2 with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- + path: ${{ env.PATH_CACHE }} + key: ${{ steps.imagetag.outputs.TAG }} - - name: Download Modules + - name: Load Docker images if exist + if: steps.cache.outputs.cache-hit == 'true' + run: | + docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}/github_coverage_1.tar + + - name: Pull base images if no-exist if: steps.cache.outputs.cache-hit != 'true' - run: go mod download + run: | + : # Pull images before build for stability + docker pull golang:1.15-alpine - - name: Run coverage (Windows) - if: runner.os == 'Windows' + - name: Build Docker images if no-exists + if: steps.cache.outputs.cache-hit != 'true' run: | - go install - go mod tidy - go vet -v ./... - go test -cover -v ./... + mkdir -p ${{ steps.imagetag.outputs.PATH_TAR }} + : # Build container images + docker-compose --file ./.github/docker-compose.yml build coverage - - name: Run coverage (Linux/macOS) - if: runner.os != 'Windows' + - name: Save built images if no-exists + if: steps.cache.outputs.cache-hit != 'true' run: | - go install - go mod tidy - /bin/bash ./.github/run-tests-coverage.sh --verbose + docker save --output ${{ steps.imagetag.outputs.PATH_TAR }}/github_coverage_1.tar github_coverage:latest + + - name: Run code coverage + run: docker-compose --file ./.github/docker-compose.yml up coverage + diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index fcbd352..2f9aa09 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -1,8 +1,10 @@ +# Workflow for static analysis and lint via golangci-lint on any push to main. # Details of this action see: https://github.com/golangci/golangci-lint-action name: golangci-lint on: workflow_dispatch: + push: jobs: golangci: @@ -13,17 +15,16 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.36 + version: latest # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. - args: --config ./.github/golangci.yml + args: --config ./.golangci.yml # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Optional: if set to true then the action will use pre-installed Go - # skip-go-installation: true + # skip-go-installation: true \ No newline at end of file diff --git a/.github/workflows/merge-tests.yaml b/.github/workflows/merge-tests.yaml deleted file mode 100644 index 35ed6c5..0000000 --- a/.github/workflows/merge-tests.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: Tests before merge - -on: - workflow_dispatch: - pull_request: - -jobs: - merge_tests: - runs-on: ubuntu-latest - steps: - - name: Checkout the code - uses: actions/checkout@v2 - - - uses: actions/setup-go@v2 - with: - go-version: 1.15.x - - - name: Use Cache - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Download Modules - if: steps.cache.outputs.cache-hit != 'true' - run: go mod download - - - name: Run shellcheck (pre-installed) - run: | - find . -name '*.sh' -type f -print0 | xargs -0 shellcheck - - - name: Install and run shfmt - run: | - GO111MODULE=on go get mvdan.cc/sh/v3/cmd/shfmt - find . -name '*.sh' -type f -print0 | xargs -0 shfmt -d - - - name: Run gofmt - uses: Jerome1337/gofmt-action@v1.0.4 - with: - gofmt-path: '.' - gofmt-flags: '-d -e' # display diffs and report all errors - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: latest - args: --config ./.github/golangci.yml - skip-go-installation: true # use pre-installed Go - - - name: Run requirement check - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - ./.github/check-requirements.sh - ./.github/run-tests-merge.sh diff --git a/.github/workflows/platform-test.yaml b/.github/workflows/platform-test.yaml new file mode 100644 index 0000000..8d1bf4d --- /dev/null +++ b/.github/workflows/platform-test.yaml @@ -0,0 +1,41 @@ +# This workflow runs the unit test (`go test ./...`) on vaious platforms, +# such as: Linux, macOS and Windows. +name: Test on macOS/Win/Linux + +on: + workflow_dispatch: + push: + +jobs: + coverage: + name: Platform test + + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-go@v2 + with: + go-version: '^1.17' + + - name: Use Cache + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Download Modules + if: steps.cache.outputs.cache-hit != 'true' + run: go mod download + + - name: Run unit tests + run: | + go mod download + go test -v ./... diff --git a/.github/workflows/update-on-release.yaml b/.github/workflows/update-codecov.yaml similarity index 79% rename from .github/workflows/update-on-release.yaml rename to .github/workflows/update-codecov.yaml index 7fce7b9..0e19247 100644 --- a/.github/workflows/update-on-release.yaml +++ b/.github/workflows/update-codecov.yaml @@ -1,3 +1,4 @@ +# Workflow to updates the coverage for CodeCov.io on release. name: Update codecov.io on: @@ -20,6 +21,6 @@ jobs: with: go-version: '1.15' - name: Run coverage - run: go test -race -coverprofile=coverage.txt -covermode=atomic + run: go test -coverprofile=coverage.txt -covermode=atomic - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/update-mod-monthly.yml b/.github/workflows/update-mod-monthly.yml new file mode 100644 index 0000000..c43c35c --- /dev/null +++ b/.github/workflows/update-mod-monthly.yml @@ -0,0 +1,49 @@ +# ============================================================================= +# Weekly Module Update +# ============================================================================= +# This workflow runs monthly to update and test the latest `go.mod` version. +# +# If all the tests succeeds to run in all Go versions, it will create a new PR +# of the `go.mod` and `go.sum`. +# +# NOTE: +# You need to set the repository secreat of `PERSONAL_ACCESS_TOKEN` environment +# variable in your repo's: +# [Settings]-[Secrets]-[Actions secrets] +# To generate a new token go to: +# https://github.com/settings/tokens +# * You need add the "repo" scope to the token but no more. +# ============================================================================= +name: Weekly Update + +on: + workflow_dispatch: + schedule: + # Runs At 19:00 UTC (AM 04:00 JST) on day-of-month 1 + - cron: '0 19 1 * *' + +jobs: + automated_build: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Update go.mod and run tests + run: | + set -eu + docker-compose --file ./.github/docker-compose.yml up tidy && \ + docker-compose --file ./.github/docker-compose.yml up v1_15 && \ + docker-compose --file ./.github/docker-compose.yml up v1_16 && \ + docker-compose --file ./.github/docker-compose.yml up v1_17 && \ + docker-compose --file ./.github/docker-compose.yml up latest + + - name: Create Pull Request on Change + uses: peter-evans/create-pull-request@v3 + with: + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/version-tests.yaml b/.github/workflows/version-tests.yaml new file mode 100644 index 0000000..9a071b9 --- /dev/null +++ b/.github/workflows/version-tests.yaml @@ -0,0 +1,77 @@ +# Workflow for various Go versions over Docker on any push to main. +# This action caches the built Docker image for a day unless any change in the +# Dockerfile was made. +name: Go 1.15~latest + +on: + workflow_dispatch: + push: + +env: + PATH_CACHE: /tmp/docker-img-arch + +jobs: + go: + name: Run tests on Go via container + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Create image tag + id: imagetag + run: | + HASH_IMAGE=${{ hashFiles('./.github/Dockerfile') }} + VARIANT=$(TZ=UTC-9 date '+%Y%m%d') + TAG="${HASH_IMAGE:0:7}:${VARIANT}" + PATH_TAR=${{ env.PATH_CACHE }}"/tar" + echo "::set-output name=TAG::${TAG}" + echo "::set-output name=PATH_TAR::${PATH_TAR}" + + - name: Cache or restore image archive + id: cache + uses: actions/cache@v2 + with: + path: ${{ env.PATH_CACHE }} + key: ${{ steps.imagetag.outputs.TAG }} + + - name: Load Docker images if exist + if: steps.cache.outputs.cache-hit == 'true' + run: | + docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_15_1.tar + docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_16_1.tar + docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_17_1.tar + docker load --input ${{ steps.imagetag.outputs.PATH_TAR }}/github_latest_1.tar + + - name: Pull base images if no-exist + if: steps.cache.outputs.cache-hit != 'true' + run: | + : # Pull images one-by-one for stability + docker pull golang:1.15-alpine + docker pull golang:1.16-alpine + docker pull golang:1.17-alpine + docker pull golang:alpine + + - name: Build Docker images if no-exists + if: steps.cache.outputs.cache-hit != 'true' + run: | + mkdir -p ${{ steps.imagetag.outputs.PATH_TAR }} + : # Build container images + docker-compose --file ./.github/docker-compose.yml build + + - name: Save built images if no-exists + if: steps.cache.outputs.cache-hit != 'true' + run: | + docker save --output ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_15_1.tar github_v1_15:latest + docker save --output ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_16_1.tar github_v1_16:latest + docker save --output ${{ steps.imagetag.outputs.PATH_TAR }}/github_v1_17_1.tar github_v1_17:latest + docker save --output ${{ steps.imagetag.outputs.PATH_TAR }}/github_latest_1.tar github_latest:latest + + - name: Run tests on Go 1.15 + run: docker-compose --file ./.github/docker-compose.yml up v1_15 + - name: Run tests on Go 1.16 + run: docker-compose --file ./.github/docker-compose.yml up v1_16 + - name: Run tests on Go 1.17 + run: docker-compose --file ./.github/docker-compose.yml up v1_17 + - name: Run tests on latest Go + run: docker-compose --file ./.github/docker-compose.yml up latest \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8fe87c2..f217460 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ *.cache - man - coverage.out -go.sum +/bin/* \ No newline at end of file diff --git a/.github/golangci.yml b/.golangci.yml similarity index 95% rename from .github/golangci.yml rename to .golangci.yml index 22c3d21..6a68e4e 100644 --- a/.github/golangci.yml +++ b/.golangci.yml @@ -52,14 +52,13 @@ linters: - godox - gofmt - gofumpt # Stricter gofmt - - golint + #- golint # Deprecated. Use revive - gosec # Inspects source code for security problems - gosimple # Linter for Go source code that specializes in simplifying a code - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - ifshort - ineffassign # Detects when assignments to existing variables are not used - lll - - maligned - megacheck # gosimple, staticcheck, unused - misspell # Find commonly misspelled English words in comments - nakedret # Limit naked returns in functions greater than a specified function length @@ -68,6 +67,7 @@ linters: - nlreturn # Requires a new line before return - prealloc # Finds slice declarations that could potentially be preallocated - predeclared # Find code that shadows one of Go's predeclared identifiers + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks - structcheck # Finds unused struct fields - stylecheck @@ -178,6 +178,13 @@ linters-settings: # if it's called for subdir of a project it can't find funcs usages. All text editor integrations # with golangci-lint call it on a directory with the changed file. check-exported: false + revive: + # see https://github.com/mgechev/revive#available-rules for details. + ignore-generated-header: true + severity: warning + rules: + - name: indent-error-flow + severity: warning testpackage: # regexp pattern to skip files. ***_internal_test.go and ***_export_test.go are the exception. skip-regexp: (export|internal)_test\.go @@ -227,4 +234,4 @@ linters-settings: - fmt.Print.* # too much log noise - ginkgo\\.F.* # these are used just for local development # Exclude godoc examples from forbidigo checks. Default is true. - exclude_godoc_examples: false + exclude_godoc_examples: false \ No newline at end of file diff --git a/.shellcheckrc b/.shellcheckrc index e5fff0c..f7ecb18 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -6,3 +6,5 @@ # Disable # SC2230: https://github.com/koalaman/shellcheck/wiki/SC2230 disable=SC2230 + +shell=bash \ No newline at end of file diff --git a/README.md b/README.md index 3a2199b..63cf808 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,40 @@ -[![Tests before merge](https://github.com/KEINOS/Hello-Cobra/workflows/Tests%20before%20merge/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions?query=workflow%3A%22Tests+before+merge%22 "View status of the action on GitHub") -[![golangci-lint](https://github.com/KEINOS/Hello-Cobra/workflows/golangci-lint/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions?query=workflow%3Agolangci-lint "View status of the action on GitHub") -[![Go Report Card](https://goreportcard.com/badge/github.com/KEINOS/Hello-Cobra)](https://goreportcard.com/report/github.com/KEINOS/Hello-Cobra "View on Go Report Card") -[![codecov](https://codecov.io/gh/KEINOS/Hello-Cobra/branch/main/graph/badge.svg?token=R2B9UBIEUI)](https://codecov.io/gh/KEINOS/Hello-Cobra "View details on CodeCov.IO") +[![go1.15+](https://img.shields.io/badge/Go-1.15,%2016,%2017,%20latest-blue?logo=go)](https://github.com/KEINOS/dev-go/actions/workflows/go-versions.yml "Supported versions") [![Go Reference](https://pkg.go.dev/badge/github.com/KEINOS/Hello-Cobra.svg)](https://pkg.go.dev/github.com/KEINOS/Hello-Cobra#section-documentation "Read generated documentation of the app") # "Hello, world!" Sample of Cobra with 100% Code Coverage -- [Cobra](https://cobra.dev/) is an awesome framework of Golang to help create CLI apps. +- [Cobra](https://cobra.dev/) is an awesome framework of Go to help create CLI apps. -**This repo is a sample of [Cobra](https://cobra.dev/)'s "`Hello, world!`" with 100% code coverage**. Including [CI](./github/workflows/)s of static analysis, lint check and formatters to just say "Hello" to the world with Cobra. It is a draft forever, so feel free to PR! +**This repo is a sample of [Cobra](https://cobra.dev/)'s "`Hello, world!`" with 100% code coverage**. + +Including [CI](./github/workflows/)s of static analysis, security scan, lint check and formatters to just say "Hello" to the world with Cobra. It is a draft forever, so feel free to PR! ## Searching for the best practices of Cobra **We all know keeping 100% of code coverage is a myth.** But as a `Golang` and `Cobra` beginner, we wanted a "Hello-world" sample with 100% of coverage, which couldn't be found by googling for "[`golang` `cobra` `sample` `hello` `world` `coverage` `100%`](https://www.google.com/search?q=%22golang%22+cobra+sample+hello+world+coverage+100%)". -This repo aims to be the basis for implementing best-practices by keeping the code coverage as high and complexity as low as possible. - -- Therefore, this is a forever-[WIP](https://en.wikipedia.org/wiki/Work_in_progress)-repo. So any PR is welcome!! - -## How to run tests - -```shellsession -$ go test -cover ./... -ok github.com/KEINOS/Hello-Cobra 0.009s coverage: 100.0% of statements -ok github.com/KEINOS/Hello-Cobra/cmd 0.353s coverage: 100.0% of statements -ok github.com/KEINOS/Hello-Cobra/util 0.015s coverage: 100.0% of statements -``` - -- When coverage was less than 100% and if you find hard which/where to cover and fix, then try: - - ```shellsession - $ # Unix/Linux users (POSIX compatible) - $ /bin/bash ./.github/run-tests-coverage.sh --verbose - ``` - -## How to run static analysis and lint for Golang - -We use [GolangCI-Lint](https://golangci-lint.run/) for static analysis and lint. - -```shellsession -$ golangci-lint run --config ./.github/golangci.yml ./... -... -``` - -- See current enabled linters: [./.github/golangci.yml](./.github/golangci.yml) - -## Pull Request (PR) +This repo aims to implement best-practices of `Cobra` but keeping the code coverage as high as possible and less complexity. -Any PR that might help Golang newbies understand is welcome. +- Therefore, this is a forever-[WIP](https://en.wikipedia.org/wiki/Work_in_progress)-repo. So any PR for the better is welcome!! -To evolve the samples through natural selection, **any PR that passes the [merge tests](https://github.com/KEINOS/Hello-Cobra/blob/main/.github/run-merge-tests.sh) will be auto-merged**. (squash and merge) +## Statuses -- [Auto-merge Conditions](https://github.com/KEINOS/Hello-Cobra/blob/main/.github/mergify.yml) - - Files changed/added were: `*.md` and/or `*.go` - - If other type of files were changed then at least 2 approved reviews are needed to merge. - - Pass the tests of "`./.github/run-merge-tests.sh`" which includes the below. - - Lint check. (`*.sh`, `*.go`) - - Static analysis. (`*.sh`, `*.go`) - - Unit tests of Go. - - 100% Code Coverage (`./.github/run-coverage-tests.sh --verbose`) - -So, if you have any better ideas, suggestions, practice, etc., then don't hesitate to PR. And if you feel something against any PR, then feel free to counter PR. - -- **We will not judge which is better**, as far as the merge-tests passes and not a prank-kind commit. -- To avoid conflict, don't forget to move on with the latest `main/master` branch before any commit. - -### Draft PR Suggested - -If you have your hands on something, then we suggest to [Draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/) first and continue. So the other people would know what you are dealing with. - -## Questions - -If you have any questions about this repo or "`Cobra`", then let us know in the [Discussions](https://github.com/KEINOS/Hello-Cobra/discussions) and find it out together. - -- https://github.com/KEINOS/Hello-Cobra/discussions - -## Issue +[![Test on macOS/Win/Linux](https://github.com/KEINOS/Hello-Cobra/actions/workflows/platform-test.yaml/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions/workflows/platform-test.yaml) +[![go1.15+](https://github.com/KEINOS/Hello-Cobra/actions/workflows/version-tests.yaml/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions/workflows/version-tests.yaml) +[![golangci-lint](https://github.com/KEINOS/Hello-Cobra/actions/workflows/golangci-lint.yaml/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions/workflows/golangci-lint.yaml) +[![codecov](https://codecov.io/gh/KEINOS/Hello-Cobra/branch/main/graph/badge.svg?token=R2B9UBIEUI)](https://codecov.io/gh/KEINOS/Hello-Cobra "View details on CodeCov.IO") +[![Go Report Card](https://goreportcard.com/badge/github.com/KEINOS/Hello-Cobra)](https://goreportcard.com/report/github.com/KEINOS/Hello-Cobra "View on Go Report Card") +[![CodeQL](https://github.com/KEINOS/Hello-Cobra/actions/workflows/codeQL-analysis.yml/badge.svg)](https://github.com/KEINOS/Hello-Cobra/actions/workflows/codeQL-analysis.yml "Vulnerability Scan") -In this repo, **only bug-kind-reports are acceptable as an issue**. Such as bug report, vulnerability, license problem, and reporting mal-attitude committer. +## Note -- https://github.com/KEINOS/Hello-Cobra/issues +- This repo is [GitHub Codespaces](https://github.com/features/codespaces) compatible. Press the `.`(dot) key to open VSCode online. (You may need to register to use Codespaces) +- This repo is [VS Code + Docker](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) compatible as well. See the [./.devcontainer/README.md](./.devcontainer/README.md) for more details. +- This repo updates monthly the `go.mod` and `go.sum` files if all the tests succeeds to run in all Go versions (Go v1.15~latest). ## License The repo itself is MIT License. (c) Copyright, [Hello-Cobra Contributors](https://github.com/KEINOS/Hello-Cobra/graphs/contributors). BUT any app created from this repo as a template/boilerplate may have its willing license. - - diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index 6a50c0e..0000000 --- a/bin/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Output directory of built/compiled application via "../build-app.sh" -# -# We do not want to version control the built binary. Though we need -# this directory for access control purposes. -* -!.gitignore diff --git a/build-app.sh b/build-app.sh index 5e120f2..75b0382 100755 --- a/build-app.sh +++ b/build-app.sh @@ -9,6 +9,9 @@ # Name of the CLI app NAME_FILE_BIN='hello-cobra' +# App Version from git tag +VERSION_APP="$(git describe --tag)" + # Path info to export the app PATH_DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)" PATH_DIR_BIN="${PATH_DIR_SCRIPT}/bin" @@ -139,6 +142,7 @@ PATH_FILE_BIN_FINAL="${PATH_FILE_BIN}-${GOOS}-${GOARCH}${GOARM_SUFFIX}" # Build as static linked binary echo '- Building static linked binary to ...' echo " ${PATH_FILE_BIN_FINAL}" +echo " Ver: ${VERSION_APP}" if CGO_ENABLED=0 \ GOOS="$GOOS" \ @@ -146,9 +150,9 @@ if CGO_ENABLED=0 \ GOARM="$GOARM" \ go build \ -installsuffix "$NAME_FILE_BIN" \ - -ldflags="-s -w -extldflags \"-static\"" \ + -ldflags="-s -w -extldflags \"-static\" -X 'main.Version=${VERSION_APP}'" \ -o="$PATH_FILE_BIN_FINAL" \ - .; then + ./hello-cobra/; then exit $SUCCESS fi echo 'Failed to build binary.' diff --git a/cmd/README.md b/cmd/README.md deleted file mode 100644 index 1ad32e4..0000000 --- a/cmd/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Commands of Hello-Cobra - -We place the command files and their tests in the same directory and level. But the actual relationships are as below. - -```text -root (root.go) ..................... The main command of Hello-Cobra. - └── hello (hello.go) ............. `hello` command is a sub-command of `root`. - └── ext (helloExtended.go) ... `ext` command is sub-command of `hello` and - grand child of `root`. -``` diff --git a/cmd/cmd_hello.go b/cmd/cmd_hello.go deleted file mode 100644 index 3aaea54..0000000 --- a/cmd/cmd_hello.go +++ /dev/null @@ -1,75 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -// ============================================================================ -// `hello` command -// ============================================================================ - -// helloCmd is the actual `hello` command, generated by `createHelloCmd()`. -// Defined since `hello` has a child command `ext` and needs to be added later. -// See the `init()` of `ext` command ("helloExtended.go"). -var helloCmd = createHelloCmd() - -// ---------------------------------------------------------------------------- -// Functions of `root` command -// ---------------------------------------------------------------------------- - -// createHelloCmd creates/generates an instance of hello command. -// Having a generator function like this, eases unit testing of a command. -// See the "hello_test.go" how. -func createHelloCmd() *cobra.Command { - cmd := &cobra.Command{ - // If the command is a child, "Use" will be the one-line message for - // "Usage:" in the help. Also note that the first word in "Use" will be - // the command name. - Use: "hello", - Short: "Says hello to the world. (Has a sub command)", - Long: `About: - 'hello' is a command that simply displays the "Hello, world!". - - But this command has a sub command 'ext' that extends it's output in various ways. - See the help of 'ext' for the details: - Hello-Cobra hello ext --help`, - Example: ` - Hello-Cobra hello - - Hello-Cobra hello -h - Hello-Cobra hello --help - - Hello-Cobra hello ext`, - // RunE runs the passed function, only when `hello` command was called. - // One of the best practices in Cobra is to use `RunE`. Instead of `Run` - // and return `error` only if an error occurs. That will ease testing. - // In that manner try not to use `os.Exit()` or `panic()` in the child - // commands but return error instead and let the main package handle it. - RunE: func(cmd *cobra.Command, args []string) error { - return sayHello(cmd) - }, - } - - return cmd -} - -// init runs on app initialization. -// Regardless of whether sub command was specified or not. -func init() { - // Add "helloCmd" command as a child of the root command(`rootCmd`). - rootCmd.AddCommand(helloCmd) - // Set default value when no conf file found. ConfUser was defined in `root.go`. - ConfUser.NameToGreet = "world" -} - -// sayHello is the main function of "hello"(helloCmd). -func sayHello(cmd *cobra.Command) error { - var to string = ConfUser.NameToGreet - - // Outputs "Hello, world!". - // We use `cobra.Command`'s `fmt.Println` wrapper to ease testing. - // Which can be changed it's output. See how: hello_test.sh - cmd.Println("Hello, " + to + "!") - - return nil -} diff --git a/cmd/cmd_helloExtended.go b/cmd/cmd_helloExtended.go deleted file mode 100644 index a5b1016..0000000 --- a/cmd/cmd_helloExtended.go +++ /dev/null @@ -1,113 +0,0 @@ -package cmd - -import ( - "strings" - - "github.com/spf13/cobra" -) - -// TypePropertyHelloExt defines the data structure to store option/flag values -// for `hello ext` command. -type TypePropertyHelloExt struct { - greetTo string - isReverse bool -} - -// propertyHello holds property values of `ext` command. -var propertyHello = &TypePropertyHelloExt{} - -// ---------------------------------------------------------------------------- -// Functions -// ---------------------------------------------------------------------------- - -// createHelloExtCmd creates the `ext` command. See the `init()` function below. -func createHelloExtCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "ext [args]", // command name and additional usage. - Short: "Extended 'hello' command.", - Long: `About: - 'ext' is a sub command of 'hello' which displays "Hello, world!" in various ways.`, - Example: ` - Hello-Cobra hello ext // Hello, world! - Hello-Cobra hello ext foo // Hello, foo! - Hello-Cobra hello ext foo bar // Hello, foo bar! - Hello-Cobra hello ext --who foo bar // Hello, foo and bar! - Hello-Cobra hello ext --who "foo bar" // Hello, foo bar! - Hello-Cobra hello ext foo bar --reverse // !rab oof ,olleH - - Hello-Cobra hello ext -h - Hello-Cobra hello ext --help`, - RunE: func(cmd *cobra.Command, args []string) error { - return runHelloExt(cmd, args) - }, - } - - // Define flags for `ext` command. - cmd.Flags(). - BoolVarP( - &propertyHello.isReverse, "reverse", "r", false, "Reverses the output.", - ) - cmd.Flags(). - StringVarP( - &propertyHello.greetTo, "who", "w", "", "Sets who to greet.", - ) - - return cmd -} - -// getMsgToGreet returns the regular greeting msg from the flag value and args. -func getMsgToGreet(args []string) string { - var ( - to string = ConfUser.NameToGreet // Set default - greetTo string = propertyHello.greetTo // get flag value - argJoined string = strings.TrimSpace(strings.Join(args, " ")) // get arg value - ) - - if greetTo != "" && argJoined != "" { - to = greetTo + " and " + argJoined - } else { - if greetTo != "" { - to = greetTo - } - - if argJoined != "" { - to = argJoined - } - } - - return "Hello, " + to + "!" -} - -// init runs on app initialization. -// Regardless of whether a command is specified or not. -func init() { - // Add `ext` command under `hello` as a child. We create and pass the command - // directly to `AddCommand()` and not exposing as a variable like `hello` does - // since `ext` won't have a child command. - helloCmd.AddCommand(createHelloExtCmd()) -} - -// reverseString reverses/flip the input string. -func reverseString(input string) string { - var msgTmp string - - for _, v := range input { - msgTmp = string(v) + msgTmp - } - - return msgTmp -} - -// runHelloExt is the main function of `ext`. -func runHelloExt(cmd *cobra.Command, args []string) error { - var greetings string = getMsgToGreet(args) - - if propertyHello.isReverse { - greetings = reverseString(greetings) - } - - // Output result - cmd.Println(greetings) - - return nil -} diff --git a/cmd/cmd_helloExtended_test.go b/cmd/cmd_helloExtended_test.go deleted file mode 100644 index e3cfaef..0000000 --- a/cmd/cmd_helloExtended_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package cmd - -import ( - "bytes" - "testing" - - "github.com/mattn/go-shellwords" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" -) - -type TDataProvider []struct { - command string // command args of the app - expect string // expected output - msgError string // message to display when the test fails -} - -// ---------------------------------------------------------------------------- -// Test Functions -// ---------------------------------------------------------------------------- - -func Test_helloExtCmd_NoFlags(t *testing.T) { - // test cases for default behavior - var cases TDataProvider = TDataProvider{ - // no options - { - command: "hello ext", expect: "Hello, world!\n", - msgError: "'Hello, world!' should return by default.", - }, - // simple arg - { - command: "hello ext foo", expect: "Hello, foo!\n", - msgError: "The arg value should be used as a name.", - }, - { - command: "hello ext foo bar", expect: "Hello, foo bar!\n", - msgError: "The arg value should be used as a name.", - }, - } - - runTestCasesForHelloCmdExt(t, cases) -} - -func Test_helloExtCmd_WhoFlag(t *testing.T) { - // test cases for who flag - var cases TDataProvider = TDataProvider{ - { - command: "hello ext -w foo", expect: "Hello, foo!\n", - msgError: "The value of option -w should be used as a name.", - }, - { - command: "hello ext --who foo", expect: "Hello, foo!\n", - msgError: "The value of option --who should be used as a name.", - }, - { - command: "hello ext --who foo bar", expect: "Hello, foo and bar!\n", - msgError: "The value of option --who and arg should be used as a name.", - }, - { - command: "hello ext -w foo bar buz", expect: "Hello, foo and bar buz!\n", - msgError: "The value of option -w and args should be used as a name.", - }, - } - - runTestCasesForHelloCmdExt(t, cases) -} - -func Test_helloExtCmd_ReverseFlag(t *testing.T) { - // test cases for reverse flag - var cases TDataProvider = TDataProvider{ - { - command: "hello ext -r", expect: "!dlrow ,olleH\n", - msgError: "The result should be reversed by '-r' opt.", - }, - { - command: "hello ext --reverse", expect: "!dlrow ,olleH\n", - msgError: "The result should be reversed by '--reverse' opt.", - }, - { - command: "hello ext -r foo", expect: "!oof ,olleH\n", - msgError: "The result should include arg and reversed by '-r' opt.", - }, - { - command: "hello ext foo -r", expect: "!oof ,olleH\n", - msgError: "The result should include arg and reversed by '-r' opt.", - }, - { - command: "hello ext -r foo bar", expect: "!rab oof ,olleH\n", - msgError: "The result should include arg and reversed by '-r' opt.", - }, - { - command: "hello ext foo -r bar", expect: "!rab oof ,olleH\n", - msgError: "The result should include arg and reversed by '-r' opt.", - }, - { - command: "hello ext foo bar -r", expect: "!rab oof ,olleH\n", - msgError: "The result should include arg and reversed by '-r' opt.", - }, - } - - runTestCasesForHelloCmdExt(t, cases) -} - -// ---------------------------------------------------------------------------- -// Helper Functions (Functions for testing) -// ---------------------------------------------------------------------------- - -func convertShellArgsToSlice(t *testing.T, str string) []string { - t.Helper() // With this call go test prints correct lines of code for failed tests. - - cmdArgs, err := shellwords.Parse(str) - if err != nil { - t.Fatalf("args parse error: %+v\n", err) - } - - if len(cmdArgs) == 0 { - t.Fatalf("args parse error. Command contains fatal strings: %+v\n", str) - } - - // `hello cmd` dependent check - if cmdArgs[0] != "hello" { - t.Fatal("format error. The command must start with 'hello'.") - } - - if cmdArgs[1] != "ext" { - t.Fatal("format error. The command must start with 'hello cmd'.") - } - - return cmdArgs[2:] // trim the first two args "hello" and "cmd". -} - -func runTestCasesForHelloCmdExt(t *testing.T, cases TDataProvider) { - t.Helper() // With this call go test prints correct lines of code for failed tests. - - var ( - helloExtCmd *cobra.Command - buffTmp = new(bytes.Buffer) - argsTmp []string - - expect string - actual string - msgErrTmp string - ) - - // Loop test cases - for _, c := range cases { - helloExtCmd = createHelloExtCmd() - argsTmp = convertShellArgsToSlice(t, c.command) - - // init - buffTmp.Reset() - helloExtCmd.SetOut(buffTmp) - helloExtCmd.SetArgs(argsTmp) - - // Run `hello ext` - if err := helloExtCmd.Execute(); err != nil { - assert.FailNowf(t, "Failed to execute 'helloExtCmd.Execute()'.", "Error msg: %v", err) - } - - expect = c.expect - actual = buffTmp.String() // resotre buffer - msgErrTmp = c.msgError - assert.Equal(t, expect, actual, msgErrTmp) - } -} diff --git a/cmd/cmd_hello_test.go b/cmd/cmd_hello_test.go deleted file mode 100644 index 4f55893..0000000 --- a/cmd/cmd_hello_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package cmd - -import ( - "bytes" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_helloCmd(t *testing.T) { - var ( - helloCmd = createHelloCmd() - argsTmp = []string{} - buffTmp = new(bytes.Buffer) - - expect string - actual string - ) - - helloCmd.SetOut(buffTmp) // set output from os.Stdout -> buffTmp - helloCmd.SetArgs(argsTmp) // set command args - - // Run `hello` command! - if err := helloCmd.Execute(); err != nil { - assert.FailNowf(t, "Failed to execute 'helloCmd.Execute()'.", "Error msg: %v", err) - } - - expect = "Hello, world!\n" - actual = buffTmp.String() // resotre buffer - assert.Equal(t, expect, actual, - "Command 'hello' should return 'Hello, world!'.", - ) -} - -func Test_helloCmd_Help(t *testing.T) { - var ( - helloCmd = createHelloCmd() - argsTmp = []string{"--help"} - buffTmp = new(bytes.Buffer) - - contains string - result string - ) - - helloCmd.SetOut(buffTmp) // set output from os.Stdout -> buffTmp - helloCmd.SetArgs(argsTmp) // set command args - - // Run `hello` command! - if err := helloCmd.Execute(); err != nil { - assert.FailNowf(t, "Failed to execute 'helloCmd.Execute()'.", "Error msg: %v", err) - } - - contains = "'hello' is a command that simply displays the \"Hello, world!\"." - result = buffTmp.String() // resotre buffer - - assert.Contains(t, result, contains, - "The command didn't include the required help message for 'hello' in it's result.", - ) -} diff --git a/cmd/cmd_root.go b/cmd/cmd_root.go deleted file mode 100644 index 9159824..0000000 --- a/cmd/cmd_root.go +++ /dev/null @@ -1,92 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/KEINOS/Hello-Cobra/conf" - "github.com/spf13/cobra" -) - -// =============================================================================== -// Root command -// =============================================================================== - -// rootCmd is the mother command of all other commands. -// Each child/sub command must register themself in their `init()` to `rootCmd`. -// - See how: hello.go -var rootCmd = createRootCmd() - -// createRootCmd creates the `root` command. -func createRootCmd() *cobra.Command { - cmdTmp := &cobra.Command{ - // The first word in "Use:" will be used as a command name. - Use: "Hello-Cobra", - // One-line description of the app. This will not appear on command help - // but in the generated document. - Short: "A sample of Cobra usage.", - // Detailed description of the app. - Long: `About: - A simple CLI app to see how Cobra works.`, - Example: ` - Hello-Cobra hello - Hello-Cobra hello ext --reverse foo bar - - Hello-Cobra hello --help - Hello-Cobra help hello - Hello-Cobra help hello ext`, - } - - // OnInitialize appends the passed function to initializers to be run when - // each command's `Execute` method was called after `init`. - cobra.OnInitialize(func() { - // Load user conf file if exists. - loadConfig(&ConfApp, &ConfUser) - }) - - // Additinal global flags of the app. - // These flags will be available to every command under root command. - cmdTmp.PersistentFlags().StringVarP( - &ConfApp.PathFileConf, - "config", - "c", - "", - "File path of config.", - ) - - // Additional flags of root command. - // Flags() is the drop-in replacement for Go's flag package to implement - // POSIX/GNU-style --flags. For the detailed usage see: - // https://github.com/spf13/pflag - cmdTmp.Flags().BoolP( - "toggle", - "t", - false, - "A flag for the main command but does nothing.", - ) - - // Return the actual root command - return cmdTmp -} - -// init runs on app initialization. Regardless of whether a command was specified -// or not. -// -// NOTE: that each child(sub commands) of root command should be added/registered -// themself in their `init()` via `rootCmd.AddCommand()`. -// See other command's `init()` func. -func init() {} - -// loadConfig sets the object in the arg with the results exits with an error if user defined conf file didn't exist. -// Otherwise searches the default file and if not found then use the default value. -func loadConfig(configApp *conf.TConfigFile, configUser interface{}) { - // Overwrite "configUser" with conf file value if file found. - if err := conf.LoadFile(*configApp, &configUser); err != nil { - // Exits if user defined conf file fails to read - if configApp.PathFileConf != "" { - msg := fmt.Errorf("failed to read configuration file.\n Error msg: %v", err) - osExit(EchoStdErrIfError(msg)) - } - // Conf file not found. Using default. Set flag to true. - configApp.IsUsingDefaultConf = true - } -} diff --git a/cmd/cmd_root_test.go b/cmd/cmd_root_test.go deleted file mode 100644 index d7a7407..0000000 --- a/cmd/cmd_root_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package cmd - -import ( - "strings" - "testing" - - "github.com/KEINOS/Hello-Cobra/conf" - "github.com/kami-zh/go-capturer" - "github.com/stretchr/testify/assert" -) - -func Test_loadConfig_UserDefinedPath_Fails(t *testing.T) { - // Save current function in osExt - oldOsExit := osExit - // restore osExit at the end - defer func() { osExit = oldOsExit }() - - var ( - expectExitCode int - actualExitCode int = 0 // This should turn into 1 - - confAppDummy conf.TConfigFile - confUserDummy struct { - NameToGreet string `mapstructure:"name_to_greet"` // // Dont'f forget to define `mapstructure` - } - ) - - // Assign mock of "osExit" to capture the exit-status-code. - osExit = func(code int) { - actualExitCode = 1 - } - - var capturedMsg string = capturer.CaptureStderr(func() { - // Test user defined bad (non-existing) file path - confAppDummy = conf.TConfigFile{ - PathFileConf: "./foobar.json", - PathDirConf: "", - NameFileConf: "", - NameTypeConf: "", - } - confUserDummy.NameToGreet = "bar" - expectExitCode = 1 - loadConfig(&confAppDummy, &confUserDummy) - }) - - // exit code assertion - assert.Equal(t, expectExitCode, actualExitCode, - "If user defined path doesn't exist then should exit with 1. Captured STDERR: "+capturedMsg, - ) - // containing error message assertion - assert.Contains(t, strings.TrimSpace(capturedMsg), "failed to read configuration file") -} - -func Test_loadConfig_UseDefault(t *testing.T) { - // Save current function in osExt - oldOsExit := osExit - // restore osExit at the end - defer func() { osExit = oldOsExit }() - - var ( - expectExitCode int - actualExitCode int - expectFlag bool - actualFlag bool - - confAppDummy conf.TConfigFile - confUserDummy struct { - NameToGreet string `mapstructure:"name_to_greet"` // // Dont'f forget to define `mapstructure` - } - ) - - // Assign mock of "osExit" to capture the exit-status-code. - osExit = func(code int) { - actualExitCode = 0 // If PathFileConf is empty then should not reach here. - } - - var capturedMsg string = capturer.CaptureStderr(func() { - // Test app defined non-existing file path - confAppDummy = conf.TConfigFile{ - PathFileConf: "", - PathDirConf: ".", - NameFileConf: "dummy_config", - NameTypeConf: "json", - } - confUserDummy.NameToGreet = "bar" - actualExitCode = 1 - expectExitCode = 1 - loadConfig(&confAppDummy, &confUserDummy) - }) - - // Exit code assertion - assert.Equal(t, expectExitCode, actualExitCode, - "If app defined conf file does not exist and using default then should not call 'osExit()'."+ - "Captured STDERR:"+capturedMsg, - ) - // Default flag assertion - expectFlag = true - actualFlag = confAppDummy.IsUsingDefaultConf - assert.Equal(t, expectFlag, actualFlag, - "Property 'TConfigApp.IsUsingDefaultConf' should be true when using default.(Only when user didn't define)", - ) -} diff --git a/cmd/cmdhello/cmd_hello.go b/cmd/cmdhello/cmd_hello.go new file mode 100644 index 0000000..a69b05a --- /dev/null +++ b/cmd/cmdhello/cmd_hello.go @@ -0,0 +1,89 @@ +/* +Package cmdhello defines the "hello" command. +*/ +package cmdhello + +import ( + "fmt" + "strings" + + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello/cmdworld" + "github.com/spf13/cobra" +) + +// ---------------------------------------------------------------------------- +// Commnad Struct +// ---------------------------------------------------------------------------- + +// Command is the struct to hold cobra.Command and it's flag options. +type Command struct { + *cobra.Command + isReverse bool // flag for "--reverse" option +} + +// ---------------------------------------------------------------------------- +// Constructor +// ---------------------------------------------------------------------------- + +// New returns the pointer of the "hello" command's singleton object. +func New() *cobra.Command { + cmdHello := &Command{ + &cobra.Command{ + Use: "hello [name [name] ...]", + Short: "Greets to the given arg.", + Long: `About: + 'hello' prints a greeting message to the given name in the arguments.`, + Example: ` + Hello-Cobra hello foo bar // Hello, foo and bar! + Hello-Cobra hello --reverse foo bar // !rab dna oof ,olleH + `, + }, + false, + } + + cmdHello.Command.RunE = cmdHello.sayHelloTo + + // Define flags for `ext` command. + cmdHello.Flags().BoolVarP( + &cmdHello.isReverse, "reverse", "r", false, "Reverses the output.", + ) + + // Add child command "world" to "hello". + cmdHello.AddCommand(cmdworld.New()) + + return cmdHello.Command +} + +// ---------------------------------------------------------------------------- +// Private Functions +// ---------------------------------------------------------------------------- + +// reverseString reverses/flip the input string. +func reverseString(input string) string { + var msgTmp string + + for _, v := range input { + msgTmp = string(v) + msgTmp + } + + return msgTmp +} + +// sayHelloTo is the main function of "hello" command. +// It will print the greetings to the given args. +func (c *Command) sayHelloTo(cmd *cobra.Command, args []string) error { + msgToGreet := "Hi!" + + if len(args) > 0 { + names := strings.Join(args, " and ") + msgToGreet = "Hello, " + names + "!" + } + + if c.isReverse { + msgToGreet = reverseString(msgToGreet) + } + + fmt.Println(msgToGreet) + + return nil +} diff --git a/cmd/cmdhello/cmd_hello_test.go b/cmd/cmdhello/cmd_hello_test.go new file mode 100644 index 0000000..54a6abe --- /dev/null +++ b/cmd/cmdhello/cmd_hello_test.go @@ -0,0 +1,70 @@ +package cmdhello_test + +import ( + "testing" + + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello" + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello/cmdworld" + "github.com/KEINOS/Hello-Cobra/cmd/cmdroot" + "github.com/kami-zh/go-capturer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ---------------------------------------------------------------------------- +// Test Functions +// ---------------------------------------------------------------------------- + +func TestNew(t *testing.T) { + obj1 := cmdhello.New() + obj2 := cmdhello.New() + + assert.NotSame(t, obj1, obj2, "it should not reference the same object") +} + +func TestNew_has_child(t *testing.T) { + mother := cmdhello.New() + children := mother.Commands() + expectChild := cmdworld.New() + + require.True(t, mother.HasSubCommands(), "command 'hello' should contain a sub-command") + assert.IsType(t, expectChild, children[0], "command 'hello' should contain 'world'") +} + +func Test_sayHelloTo(t *testing.T) { + for _, test := range []struct { + expect string + args []string + }{ + { + "Hi!", []string{}, + }, + { + "!iH", []string{"--reverse"}, + }, + { + "About:", []string{"--help"}, + }, + { + "Hello, foo and bar!", []string{"foo", "bar"}, + }, + { + "!dlrow ,olleH", []string{"world", "--reverse"}, + }, + } { + args := append([]string{"hello"}, test.args...) + + mother := cmdroot.New("v0.0.0-" + t.Name()) + mother.SetArgs(args) + + out := capturer.CaptureOutput(func() { + err := mother.Execute() + + require.NoError(t, err, + "Input args: %v\nExpect out: %v\n", test.args, test.expect) + }) + + assert.Contains(t, out, test.expect, + "Expect: %v\nActual: %v\n", test.expect, out) + } +} diff --git a/cmd/cmdhello/cmdworld/cmd_world.go b/cmd/cmdhello/cmdworld/cmd_world.go new file mode 100644 index 0000000..6ec239b --- /dev/null +++ b/cmd/cmdhello/cmdworld/cmd_world.go @@ -0,0 +1,82 @@ +/* +Package cmdworld defines the "world" command which is the child of "hello" command. +*/ +package cmdworld + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// ---------------------------------------------------------------------------- +// Commnad Struct +// ---------------------------------------------------------------------------- + +// Command is the struct to hold cobra.Command and it's flag options. +type Command struct { + *cobra.Command + isReverse bool // flag for "--reverse" option +} + +// ---------------------------------------------------------------------------- +// Public Functions +// ---------------------------------------------------------------------------- + +// New returns the pointer of the "world" command's singleton object. +func New() *cobra.Command { + // Create the object of the command with default flags. + cmdWorld := &Command{ + &cobra.Command{ + Use: "world", + Short: "Says hello to the world.", + Long: `About: + 'world' is a sub command of 'hello' which displays "Hello, world!".`, + Example: ` + Hello-Cobra hello world // Hello, world! + Hello-Cobra hello world --reverse // !dlrow ,olleH + `, + }, + false, + } + + // Add method to RunE + cmdWorld.RunE = cmdWorld.sayHelloWorld + + // Define flags for `ext` command. + cmdWorld.Flags().BoolVarP( + &cmdWorld.isReverse, "reverse", "r", cmdWorld.isReverse, "Reverses the output.", + ) + + return cmdWorld.Command +} + +// ---------------------------------------------------------------------------- +// Private Functions +// ---------------------------------------------------------------------------- + +// reverseString reverses/flip the input string. +func reverseString(input string) string { + var msgTmp string + + for _, v := range input { + msgTmp = string(v) + msgTmp + } + + return msgTmp +} + +// sayHelloWorld is the main function of "world" command. +// It prints "Hello, world!" or if "--reverse" flag was set then prints +// "!dlrow ,olleH". +func (c *Command) sayHelloWorld(cmd *cobra.Command, args []string) error { + msgToGreet := "Hello, world!" + + if c.isReverse { + msgToGreet = reverseString(msgToGreet) + } + + fmt.Println(msgToGreet) + + return nil +} diff --git a/cmd/cmdhello/cmdworld/cmd_world_test.go b/cmd/cmdhello/cmdworld/cmd_world_test.go new file mode 100644 index 0000000..452048e --- /dev/null +++ b/cmd/cmdhello/cmdworld/cmd_world_test.go @@ -0,0 +1,46 @@ +package cmdworld_test + +import ( + "testing" + + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello/cmdworld" + "github.com/KEINOS/Hello-Cobra/cmd/cmdroot" + "github.com/kami-zh/go-capturer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ---------------------------------------------------------------------------- +// Test Functions +// ---------------------------------------------------------------------------- + +func TestNew(t *testing.T) { + obj1 := cmdworld.New() + obj2 := cmdworld.New() + + assert.NotSame(t, obj1, obj2, "it should not reference the same object") +} + +func Test_sayHelloWorld(t *testing.T) { + for _, test := range []struct { + expect string + args []string + }{ + {"Hello, world!", []string{}}, + {"!dlrow ,olleH", []string{"-r"}}, + {"!dlrow ,olleH", []string{"--reverse"}}, + } { + args := append([]string{"hello", "world"}, test.args...) + + grandMother := cmdroot.New("v0.0.0-" + t.Name()) + grandMother.SetArgs(args) + + out := capturer.CaptureOutput(func() { + err := grandMother.Execute() + + require.NoError(t, err) + }) + + assert.Contains(t, out, test.expect) + } +} diff --git a/cmd/cmdroot/cmd_root.go b/cmd/cmdroot/cmd_root.go new file mode 100644 index 0000000..9f6d197 --- /dev/null +++ b/cmd/cmdroot/cmd_root.go @@ -0,0 +1,58 @@ +package cmdroot + +import ( + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello" + "github.com/spf13/cobra" +) + +// ---------------------------------------------------------------------------- +// Commnad Struct +// ---------------------------------------------------------------------------- + +// Command is the struct to hold cobra.Command and it's flag options. +type Command struct { + *cobra.Command +} + +// ---------------------------------------------------------------------------- +// Public Functions +// ---------------------------------------------------------------------------- + +// New returns a new "root" command object. +// +// The argument appVersion will be used when "--version" option was called. +func New(appVersion string) *cobra.Command { + if appVersion == "" { + appVersion = "(unknown)" + } + + cmdRoot := &Command{ + &cobra.Command{ + // The first word in "Use:" will be used as a command name. + Use: "hello-cobra", + // One-line description of the app. This will not appear on command help + // but in the generated document. + Short: "A sample of Cobra usage.", + // Detailed description of the app. + Long: `About: + A simple CLI app to see how Cobra works.`, + Example: ` + Hello-Cobra hello + Hello-Cobra hello ext --reverse foo bar + + Hello-Cobra hello --help + Hello-Cobra help hello + Hello-Cobra help hello ext`, + Version: appVersion, + }, + } + + cmdRoot.PersistentFlags().Bool("verbose", false, "displays debug info if any") + + // Add child commands to the "root" command. + cmdRoot.AddCommand( + cmdhello.New(), // Add "hello" command (with "world" grand child command) + ) + + return cmdRoot.Command +} diff --git a/cmd/cmdroot/cmd_root_test.go b/cmd/cmdroot/cmd_root_test.go new file mode 100644 index 0000000..b408b2d --- /dev/null +++ b/cmd/cmdroot/cmd_root_test.go @@ -0,0 +1,48 @@ +package cmdroot_test + +import ( + "testing" + + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello" + "github.com/KEINOS/Hello-Cobra/cmd/cmdhello/cmdworld" + "github.com/KEINOS/Hello-Cobra/cmd/cmdroot" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ---------------------------------------------------------------------------- + +func TestNew(t *testing.T) { + obj1 := cmdroot.New("v0.0.0-" + t.Name()) + obj2 := cmdroot.New("v0.0.0-" + t.Name()) + + assert.NotSame(t, obj1, obj2, "it should not reference the same object") +} + +func TestNew_empty_version(t *testing.T) { + mother := cmdroot.New("") + + expect := "(unknown)" + actual := mother.Version + + assert.Equal(t, expect, actual) +} + +func TestNew_has_child(t *testing.T) { + mother := cmdroot.New("v0.0.0-" + t.Name()) + + require.True(t, mother.HasSubCommands(), + "new root object should contain sub-command/s") + + children := mother.Commands() + expectChild := cmdhello.New() + + require.IsType(t, expectChild, children[0], + "command 'root' should contain 'hello' as a child") + + grandChildren := children[0].Commands() + expectGrandChild := cmdworld.New() + + assert.IsType(t, expectGrandChild, grandChildren[0], + "command 'root' should contain 'world' as a grandchild") +} diff --git a/cmd/common.go b/cmd/common.go deleted file mode 100644 index 1c73155..0000000 --- a/cmd/common.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -Package cmd is the package of the actual commands of this CLI app. -*/ -package cmd - -import ( - "fmt" - "os" - - "github.com/KEINOS/Hello-Cobra/conf" -) - -// =============================================================================== -// Constants -// =============================================================================== - -const ( - // SUCCESS is an alias of exit status code to ease read. - SUCCESS int = 0 - // FAILURE is an alias of exit status code to ease read. - FAILURE int = 1 -) - -// =============================================================================== -// Application Settings -// =============================================================================== - -// TConfUser defines the data structure to store values from a conf file. Viper -// will read these values from the config file or env variables. `mapstructure` -// defines the key name in the conf file. -// Ex) `{"name_to_greet": "foo"}` will be `NameToGreet = "foo"``` -type TConfUser struct { - NameToGreet string `mapstructure:"name_to_greet"` -} - -var ( - // ConfApp is the basic app settings. - ConfApp = conf.TConfigFile{ - PathDirConf: ".", - NameFileConf: "config", - NameTypeConf: "json", - PathFileConf: "", // User defined file path - IsUsingDefaultConf: false, // Set to true if conf file not fond - } - - // ConfUser holds the values read from the config file. The values here are - // the default. - ConfUser = TConfUser{ - NameToGreet: "", // Conf for `hello` and `hello ext` command. - } -) - -// osExit is a copy of `os.Exit` to ease the "exit status" test. -// See: https://stackoverflow.com/a/40801733/8367711 -var osExit = os.Exit - -// ============================================================================ -// Exported functions of cmd package. (https://tour.golang.org/basics/3) -// -// Define functions that were used outside this package such as `main` pkg. -// These will work as `cmd` method. -// Ex) cmd.EchoStdErrIfError(err) -// ============================================================================ - -// EchoStdErrIfError is an STDERR wrappter and returns 0(zero) or 1. -// It does nothing if the error is nil and returns 0. -func EchoStdErrIfError(err error) int { - if err != nil { - fmt.Fprintln(os.Stderr, err) - - return FAILURE - } - - return SUCCESS -} - -// Execute is the main function of `cmd` package. -// It adds all the child commands to the root's command tree and sets their flag -// settings. Then runs/executes the `rootCmd` to find appropriate matches for child -// commands with corresponding flags and args. -// -// Usually `cmd.Execute` will be called by the `main.main()` and it only needs to -// happen once to the rootCmd. -// Returns `error` when it fails to execute. -func Execute() error { - // Read conf file values to ConfUser with ConfApp settings - return rootCmd.Execute() -} diff --git a/cmd/common_test.go b/cmd/common_test.go deleted file mode 100644 index 9d8cbaa..0000000 --- a/cmd/common_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package cmd - -import ( - "errors" - "testing" - - "github.com/kami-zh/go-capturer" - "github.com/stretchr/testify/assert" -) - -func TestEchoStdErrIfError(t *testing.T) { - var ( - expectStatus int = 1 - actualStatus int - expectMsg string = "foo bar" - actualMsg string - errorMsg error = errors.New(expectMsg) - ) - - // Run the function and capture the STDERR msg and it's returned int value. - actualMsg = capturer.CaptureStderr(func() { - actualStatus = 0 // This should turn into 1 - actualStatus = EchoStdErrIfError(errorMsg) - }) - - assert.Equal(t, expectStatus, actualStatus, "Error code should return 1") - assert.Equal(t, expectMsg+"\n", actualMsg, "The two words should be the same.") -} - -func TestEchoStdErrIfError_IsNil(t *testing.T) { - var ( - expectStatus int = 0 - actualStatus int = 1 // This should turn into 0 - expectMsg string = "" - actualMsg string - ) - - actualMsg = capturer.CaptureStderr(func() { - actualStatus = EchoStdErrIfError(nil) - }) - - assert.Equal(t, expectStatus, actualStatus, "Error code should return 1") - assert.Equal(t, expectMsg, actualMsg, - "If the arg is nil it should not print enything to STDERR. STDERR Msg: "+actualMsg, - ) -} - -func TestExecute(t *testing.T) { - var ( - result string - contains string - ) - - result = capturer.CaptureStdout(func() { - if err := Execute(); err != nil { - assert.FailNowf(t, "Failed to execute 'root.Execute()'.", "Error msg: %v", err) - } - }) - contains = "A simple CLI app to see how Cobra works." - - assert.Contains(t, result, contains, "When no arg, should return help message.") -} diff --git a/conf/README.md b/conf/README.md deleted file mode 100644 index 94b3b7f..0000000 --- a/conf/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# package conf - -Package `conf` reads values from a configuration file such as JSON, TOML, YAML and etc using [Viper](https://github.com/spf13/viper). - -- Note: Eventhough this package may be used as a file loader of JSON/YAML/etc but it's NOT designed to load a huge-size-file. - -## Sample usage - -Pretend the user config file (`userConfig.json`) was as below: - -```json -{ - "my_value": "Cobra" -} -``` - -Then the Go sample to load it would be as below: - -```go -package cmd - -import "github.com/KEINOS/Hello-Cobra/conf" - -// TDataUser defines the data structure to be stored from the conf file read. -type TDataUser struct { - // MyValue is the variable to store the value of "my_value" key in the conf file. - MyValue string `mapstructure:"my_value"` -} - -var ( - configFile = conf.TConfigFile { - PathDirConf: ".", - NameFileConf: "userConfig", - NameTypeConf: "json", - } - - userValues = TDataUser { - MyValue: "default value", - } -) - -// Load value from "./userConfig.json" to `userValues` -if err := conf.LoadConfig(*configFile, &userValues); err != nil { - // do something with the error -} - -// Use loaded value from the conf file -myValue := userValues.MyValue - -// `myValue` expects to be "Cobra". If "./userConfig.json" didn't exist -// then the value should be "default value". - -``` - -## TODO - -- [ ] Map the values from ENV variables that match diff --git a/conf/config.go b/conf/config.go deleted file mode 100644 index 972b42e..0000000 --- a/conf/config.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Package conf reads values from a configuration file such as JSON, TOML, YAML and etc using Viper. - -It was separated to a different package from `cmd` to ease testing and re-use. - -- This package was very much taught by the below articles: - - "[Backend #12] Load config from file & environment variables in Golang with Viper" - - https://youtu.be/n5p8HkO6bnE by TECH SCHOOL @ YouTube - - "cobra-viper-example" - - https://github.com/nirasan/cobra-viper-example @ GitHub - -*/ -package conf - -import ( - "path/filepath" - "strings" - - "github.com/spf13/viper" -) - -// TConfigApp is an alias of TConfigFile for backward compatibility and it will be removed in v1.3.0. -// Deprecated: as of v1.2.1. Use TConfigFile instead. -type TConfigApp = TConfigFile - -// TConfigFile defines the data structure to store the information of the file to load. -type TConfigFile struct { - /* one-liner-conf of file path */ - - PathFileConf string // File path of config. If set, will have priority than NameFileConf and PathDirConf. - - /* parted conf of file path */ - - PathDirConf string // Dir path of config to search. - NameFileConf string // File name of config file. May or may not have an extension. - NameTypeConf string // File extension. REQUIRED if the conf file does not have the extension. - - /* configuration of it's state */ - IsUsingDefaultConf bool // Flag to determine if the app is using the default value or conf file value. -} - -// GetNameConf is a method of TConfigFile that returns the config file name. -func (c TConfigFile) GetNameConf() string { - if c.PathFileConf != "" { - return filepath.Base(c.PathFileConf) - } - - if hasExtInName(c.NameFileConf) { - return c.NameFileConf - } - - return c.NameFileConf + "." + c.NameTypeConf -} - -// LoadConfig is an alias of LoadFile for backward compatibility and it will be removed in v1.3.0. -// Deprecated: as of v1.2.1. Use LoadFile() instead. -var LoadConfig = LoadFile - -// LoadFile() stores values from the config file to userConfig. -// -// @args -// appConfig TConfigFile : Basic configuration to read the conf file. -// userConfig struct : An object to store values from conf file. -// @return -// err error : If fails to read/store values from conf file returns error othersise nil. -// Usage: -// type TConfUser struct { -// MyValue string `mapstructure:"my_value"` -// } -// var ( -// configApp = conf.TConfigFile{ -// PathDirConf: ".", -// NameFileConf: "userConfig", -// NameTypeConf: "json", -// } -// configUser = TConfUser{ -// MyValue: "", -// } -// ) -// if err := conf.LoadFile(*configApp, &configUser); err != nil { -// // do something with the error -// } -// myValue := configUser.MyValue -func LoadFile(appConfig TConfigFile, userConfig interface{}) (err error) { - // pitViper is a temporary viper instance - pitViper := viper.New() - - // Set file path to search - if appConfig.PathFileConf != "" { - // Set one-liner file path - pitViper.SetConfigFile(appConfig.PathFileConf) - } else { - // Set inidividual file path info - pitViper.AddConfigPath(appConfig.PathDirConf) - pitViper.SetConfigName(appConfig.NameFileConf) - pitViper.SetConfigType(appConfig.NameTypeConf) - } - - // Search and read values from the config file and stores to "userConfig" - err = pitViper.ReadInConfig() - if err == nil { - // Map the read config values - err = pitViper.Unmarshal(&userConfig) - } - - return err // return error if viper fails to read or map the values -} - -// hasExtInName() returns true if the nameFile contains a file extension which viper can detect. -func hasExtInName(nameFile string) bool { - var extWithNoDot string = strings.TrimLeft(filepath.Ext(nameFile), ".") - - return hasStringInSlice(extWithNoDot, viper.SupportedExts) -} - -// hasStringInSlice returns true if a string is in a list of slice. -func hasStringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - - return false -} diff --git a/conf/config_test.go b/conf/config_test.go deleted file mode 100644 index ae34c39..0000000 --- a/conf/config_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package conf - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetNameConf_Regular(t *testing.T) { - var ( - confAppTmp = TConfigFile{ - PathDirConf: ".", - NameFileConf: "sample_config", - NameTypeConf: "json", - } - expect = "sample_config.json" - actual = confAppTmp.GetNameConf() - ) - - assert.Equal(t, expect, actual, "Returned unexpected value from the conf.") -} - -func TestGetNameConf_WithExtInName(t *testing.T) { - var ( - confApp TConfigFile - expect string - actual string - ) - - // User defined conf file path - confApp = TConfigFile{ - PathFileConf: "./foo/bar.json", // Value from the command flag - PathDirConf: ".", // This should be ignored - NameFileConf: "sample_config", // This should be ignored - NameTypeConf: "json", // This should be ignored - } - expect = "bar.json" - actual = confApp.GetNameConf() - assert.Equal(t, expect, actual, "If the conf name has an extension it should return as is.") - - // App defined conf file - confApp = TConfigFile{ - PathFileConf: "", - PathDirConf: ".", - NameFileConf: "foo.yaml", - NameTypeConf: "json", // This should be ignored - } - expect = "foo.yaml" - actual = confApp.GetNameConf() - assert.Equal(t, expect, actual, "If the conf name has an extension it should return as is.") -} - -func Test_hasExtInName_Failure(t *testing.T) { - var ( - nameExt string = "sample_conf.html" - expect bool = false - actual bool = hasExtInName(nameExt) - ) - - assert.Equal(t, expect, actual, "Un-available extension for Viper should return false.") -} - -func Test_hasExtInName_Regular(t *testing.T) { - var ( - nameFile string = "sample_conf.json" - expect bool = true - actual bool = hasExtInName(nameFile) - ) - - assert.Equal(t, expect, actual, "Available extension for Viper should return true.") -} - -func TestLoadFile_Failure(t *testing.T) { - var ( - ConfApp = TConfigFile{ - PathFileConf: "./foobar.json", - PathDirConf: "..", - NameFileConf: "sample_config", - NameTypeConf: "json", - } - - ConfUser = struct { - NameToGreet string - }{ - NameToGreet: "bar", - } - ) - - err := LoadFile(ConfApp, ConfUser) - assert.Error(t, err, "When un-existing path was provided it should return an error.") -} - -func TestLoadFile_Regular(t *testing.T) { - var ( - confApp TConfigFile - confUser struct { - NameToGreet string `mapstructure:"name_to_greet"` - } - err error - expect string - actual string - ) - - // User defined config file - confApp = TConfigFile{ - PathFileConf: "../sample_config.json", - } - confUser.NameToGreet = "bar" - - if err = LoadFile(confApp, &confUser); err != nil { - t.Fatalf("Failed to read conf file for test.\nError msg: %v", err) - } - - expect = "Cobra" - actual = confUser.NameToGreet - assert.Equal(t, expect, actual, "Returned unexpected value from the conf.") - - // App defined config file - confApp = TConfigFile{ - PathFileConf: "", - PathDirConf: "..", - NameFileConf: "sample_config", - NameTypeConf: "json", - } - confUser.NameToGreet = "bar" - - if err = LoadFile(confApp, &confUser); err != nil { - t.Fatalf("Failed to read conf file for test.\nError msg: %v", err) - } - - expect = "Cobra" - actual = confUser.NameToGreet - assert.Equal(t, expect, actual, "Returned unexpected value from the conf.") -} diff --git a/go.mod b/go.mod index b6f49f4..c786a47 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,11 @@ module github.com/KEINOS/Hello-Cobra go 1.15 -// For the other packages to ease development see: .devcontainer/Dockerfile require ( github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d - github.com/mattn/go-shellwords v1.0.11 - github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.1 + github.com/mattn/go-shellwords v1.0.12 + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/cobra v1.2.1 + github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1079b27 --- /dev/null +++ b/go.sum @@ -0,0 +1,695 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go 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.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +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= +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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/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/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +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-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/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/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +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/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0= +github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/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.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +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.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +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= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +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= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +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-20210817164053-32db794688a5/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/lint v0.0.0-20210508222113-6edffad5e616/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/mod v0.4.2/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190923162816-aa69164e4478/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-20201110031124-69a78807bb2b/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-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/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/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-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-20190907020128-2ca718005c18/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-20191112195655-aa38f8e97acc/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-20200619180055-7c47624df98f/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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +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-20200513103714-09dca8ec2884/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-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +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/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= +gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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/hello-cobra/helper_of_test.go b/hello-cobra/helper_of_test.go new file mode 100644 index 0000000..25484a2 --- /dev/null +++ b/hello-cobra/helper_of_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "os" + "testing" +) + +// ---------------------------------------------------------------------------- +// Helper Functions +// ---------------------------------------------------------------------------- + +// MockOsExit mocks the OsExit behavior. Which is a copy of os.Exit(). +func MockOsExit(t *testing.T, capturedExitCode *int) (deferFunction func()) { + t.Helper() + + // Save current function and restore at the end. + oldOsExit := OsExit + + // Mock OsExit which captures the exit code + OsExit = func(code int) { + *capturedExitCode = code + } + + return func() { + OsExit = oldOsExit + } +} + +// MockOsArgs temporary replaces the os.Args values during test. +func MockOsArgs(t *testing.T, args []string) (deferFunction func()) { + t.Helper() + + // Backup value + oldOsArgs := os.Args + + // Mock the args value + os.Args = append([]string{t.Name()}, args...) + + return func() { + os.Args = oldOsArgs + } +} diff --git a/hello-cobra/main.go b/hello-cobra/main.go new file mode 100644 index 0000000..055ceb2 --- /dev/null +++ b/hello-cobra/main.go @@ -0,0 +1,64 @@ +/* +Package main. +*/ +package main + +import ( + "fmt" + "os" + "runtime/debug" + + "github.com/KEINOS/Hello-Cobra/cmd/cmdroot" +) + +// FAILURE is an alias of exit status code to ease read. +const FAILURE = 1 + +// OsExit is a copy of `os.Exit` to ease mocking during test. +// Ref: https://stackoverflow.com/a/40801733/8367711 +// +// We found useful that `os.Exit` should be called only in main package. +// And functions or methods in other packages should return an error rather than +// exiting the app. To do so, it was way easy to test them. +var OsExit = os.Exit + +// Version is the application version to display. This value should be set via +// `-ldflags` during build. +// +// Sample command to build: +// +// $ VER_APP="v1.0.0" +// $ go build -ldflags="-X 'main.Version=${VER_APP}'" -o foo ./hello-cobra/ +// $ ./foo --version +// hello-cobra version v1.0.0 +var Version string + +// DebugReadBuildInfo is a copy of debug.ReadBuildInfo to ease testing. +var DebugReadBuildInfo = debug.ReadBuildInfo + +// ---------------------------------------------------------------------------- +// Main +// ---------------------------------------------------------------------------- + +func main() { + // Initialize the app then executes the designated command. + if err := cmdroot.New(GetVersion()).Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + OsExit(FAILURE) + } +} + +// GetVersion returns the app version. +func GetVersion() string { + // Via build flag + if Version != "" { + return Version + } + + // Via commit tag + if buildInfo, ok := DebugReadBuildInfo(); ok { + return buildInfo.Main.Version + } + + return "(unknown)" +} diff --git a/hello-cobra/main_test.go b/hello-cobra/main_test.go new file mode 100644 index 0000000..d45af96 --- /dev/null +++ b/hello-cobra/main_test.go @@ -0,0 +1,140 @@ +//nolint:testpackage // To override osExit the test needs to be part of main +package main + +import ( + "runtime/debug" + "testing" + + "github.com/kami-zh/go-capturer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ---------------------------------------------------------------------------- +// Tests for main's behavior +// ---------------------------------------------------------------------------- + +func Test_main(t *testing.T) { + // Run main() and capture STDOUT message and its exit-status-code + out := capturer.CaptureOutput(func() { + main() + }) + + // Assertion + expectMsg := "Usage:" // Help msg includes this string + + assert.Contains(t, out, expectMsg, "it should contain help message if no args") +} + +func Test_unknown_command(t *testing.T) { + // Mock the "OsExit" to capture the exit-status-code + actualExitCode := 0 // this shuld turn to 1 + expectExitCode := 1 + + recoverOsExit := MockOsExit(t, &actualExitCode) + defer recoverOsExit() + + // Mock args + recoverOsArgs := MockOsArgs(t, []string{ + "foobar", // unknown command + }) + defer recoverOsArgs() + + // Capture STDERR + out := capturer.CaptureStderr(func() { + main() + }) + + assert.Contains(t, out, "Error: unknown command \"foobar\"") + require.Equal(t, expectExitCode, actualExitCode, + "unknown command should exit with status 1") +} + +func Test_version_flag(t *testing.T) { + // Mock args. + recoverOsArgs := MockOsArgs(t, []string{ + "--version", + }) + defer recoverOsArgs() + + out := capturer.CaptureOutput(func() { + main() + }) + + assert.Contains(t, out, "hello-cobra version (devel)") +} + +// ---------------------------------------------------------------------------- +// Function Test +// ---------------------------------------------------------------------------- + +func TestGetVersion_version_not_set(t *testing.T) { + // Backup and restore Version before and after the test + oldVersion := Version + defer func() { + Version = oldVersion + }() + + Version = "" // Mock Version as empty + + // Backup and restore DebugReadBuildInfo + oldDebugReadBuildInfo := DebugReadBuildInfo + defer func() { + DebugReadBuildInfo = oldDebugReadBuildInfo + }() + + // Mock debug.BuildInfo to return false + DebugReadBuildInfo = func() (info *debug.BuildInfo, ok bool) { + return nil, false + } + + expect := "(unknown)" + actual := GetVersion() + + assert.Equal(t, expect, actual) +} + +func TestGetVersion_version_via_build_info(t *testing.T) { + // Backup and recover Version before and after the test + oldVersion := Version + defer func() { + Version = oldVersion + }() + + Version = "" // Mock Version as empty + + oldDebugReadBuildInfo := DebugReadBuildInfo + defer func() { + DebugReadBuildInfo = oldDebugReadBuildInfo + }() + + expect := "foobar" // Mock build info's version + + // Mock DebugReadBuildInfo + DebugReadBuildInfo = func() (info *debug.BuildInfo, ok bool) { + i := &debug.BuildInfo{ + Main: debug.Module{ + Version: expect, + }, + } + + return i, true + } + + assert.Equal(t, expect, GetVersion()) +} + +func TestGetVersion_version_via_build_flag(t *testing.T) { + // Backup and recover Version before and after the test + oldVersion := Version + defer func() { + Version = oldVersion + }() + + Version = "v0.0.0-" + t.Name() + + expect := Version + actual := GetVersion() + + assert.Equal(t, expect, actual, "it should return the main.Version value if set") +} diff --git a/main.go b/main.go deleted file mode 100644 index 25d5c32..0000000 --- a/main.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Package main main.go is the entry point to run `cmd.Execute()` in the package "cmd". - -It will exit the app with the staus code 0(zero) for success or 1 for failure according to the command's result. - -If no command was specified then will show the help. -*/ -package main - -import ( - "os" - - "github.com/KEINOS/Hello-Cobra/cmd" -) - -// osExit is a copy of `os.Exit` to ease the "exit status" test. -// See: https://stackoverflow.com/a/40801733/8367711 -var osExit = os.Exit - -func main() { - // `cmd.Execute()` initializes the app such as adding sub-commands to the root command. - // Then executes the designated command. - // If `cmd.Execute()` returns an error then it will print the error message to STDERR and exits with 1. - osExit(cmd.EchoStdErrIfError(cmd.Execute())) -} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index c981916..0000000 --- a/main_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Package main main_test.go is the test for main.go. - -We found the easy way to test `main` was to capture it's STDOUT or STDERR and -override the `osExit` which is an alias of `os.Exit` set in `main.go`. -*/ -package main //nolint:testpackage // To override osExit the test needs to be part of main - -import ( - "testing" - - "github.com/kami-zh/go-capturer" - "github.com/stretchr/testify/assert" -) - -func Test_main(t *testing.T) { - // Save current function and restore at the end - oldOsExit := osExit - - defer func() { osExit = oldOsExit }() - - var ( - expectExitCode = 0 - expectMsg = "Usage:" // Help msg includes this string - - actualMsg string - actualExitCode int - - // Mock of "osExit" to capture the exit-status-code. - myExit = func(code int) { - actualExitCode = code - } - ) - - // Assign the mock - osExit = myExit - // Run main() to capture STDOUT message and its exit-status-code - actualMsg = capturer.CaptureOutput(func() { - main() - }) - - // Assertion - assert.Equal(t, actualExitCode, expectExitCode, "Unexpected exit code.") - assert.Contains(t, actualMsg, expectMsg, "Should contain help message") -} diff --git a/sample_config.json b/testdata/sample_config.json similarity index 100% rename from sample_config.json rename to testdata/sample_config.json