diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..6d0b6d3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.go]
+indent_style = tab
+
+[{Makefile,*.mk}]
+indent_style = tab
diff --git a/.github/.editorconfig b/.github/.editorconfig
new file mode 100644
index 0000000..0902c6a
--- /dev/null
+++ b/.github/.editorconfig
@@ -0,0 +1,2 @@
+[{*.yml,*.yaml}]
+indent_size = 2
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
new file mode 100644
index 0000000..db00ae6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -0,0 +1,119 @@
+name: π Bug report
+description: Report a bug to help us improve Viper
+labels: [kind/bug]
+body:
+- type: markdown
+ attributes:
+ value: |
+ Thank you for submitting a bug report!
+
+ Please fill out the template below to make it easier to debug your problem.
+
+ If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://github.com/spf13/viper/issues/new/choose).
+- type: checkboxes
+ attributes:
+ label: Preflight Checklist
+ description: Please ensure you've completed all of the following.
+ options:
+ - label: I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success.
+ required: true
+ - label: I am not looking for support or already pursued the available [support channels](https://github.com/spf13/viper/issues/new/choose) without success.
+ required: true
+ - label: I have checked the [troubleshooting guide](https://github.com/spf13/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success.
+ required: true
+- type: input
+ attributes:
+ label: Viper Version
+ description: What version of Viper are you using?
+ placeholder: 1.8.1
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: Go Version
+ description: What version of Go are you using?
+ placeholder: "1.16"
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: Config Source
+ description: What sources do you load configuration from?
+ options:
+ - Manual set
+ - Flags
+ - Environment variables
+ - Files
+ - Remove K/V stores
+ - Defaults
+ multiple: true
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: Format
+ description: Which file formats do you use?
+ options:
+ - JSON
+ - YAML
+ - TOML
+ - Dotenv
+ - HCL
+ - Java properties
+ - INI
+ - Other (specify below)
+ multiple: true
+- type: input
+ attributes:
+ label: Repl.it link
+ description: Complete example on Repl.it reproducing the issue. [Here](https://repl.it/@sagikazarmark/Viper-example) is an example you can use.
+ placeholder: https://repl.it/@sagikazarmark/Viper-example
+- type: textarea
+ attributes:
+ label: Code reproducing the issue
+ description: Please provide a Repl.it link if possible.
+ render: go
+ placeholder: |
+ package main
+
+ import (
+ "github.com/spf13/viper"
+ )
+
+ func main() {
+ v := viper.New()
+
+ // ...
+
+ var config Config
+
+ err = v.Unmarshal(&config)
+ if err != nil {
+ panic(err)
+ }
+ }
+- type: textarea
+ attributes:
+ label: Expected Behavior
+ description: A clear and concise description of what you expected to happen.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Actual Behavior
+ description: A clear description of what actually happens.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior if it is not self-explanatory.
+ placeholder: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+- type: textarea
+ attributes:
+ label: Additional Information
+ description: Links? References? Anything that will give us more context about the issue that you are encountering!
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..cd537e8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,13 @@
+blank_issues_enabled: false
+contact_links:
+ - name: β Ask a question
+ url: https://github.com/spf13/viper/discussions/new?category=q-a
+ about: Ask and discuss questions with other Viper community members
+
+ - name: π Reference
+ url: https://pkg.go.dev/mod/github.com/spf13/viper
+ about: Check the Go code reference
+
+ - name: π¬ Slack channel
+ url: https://gophers.slack.com/messages/viper
+ about: Please ask and answer questions here
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
new file mode 100644
index 0000000..c84aeb2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -0,0 +1,39 @@
+name: π Feature request
+description: Suggest an idea for Viper
+labels: [kind/enhancement]
+body:
+- type: markdown
+ attributes:
+ value: |
+ Thank you for submitting a feature request!
+
+ Please describe what you would like to change/add and why in detail by filling out the template below.
+
+ If you are not sure if your request fits into Viper, you can contact us via the available [support channels](https://github.com/spf13/viper/issues/new/choose).
+- type: checkboxes
+ attributes:
+ label: Preflight Checklist
+ description: Please ensure you've completed all of the following.
+ options:
+ - label: I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success.
+ required: true
+- type: textarea
+ attributes:
+ label: Problem Description
+ description: A clear and concise description of the problem you are seeking to solve with this feature request.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Proposed Solution
+ description: A clear and concise description of what would you like to happen.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Alternatives Considered
+ description: A clear and concise description of any alternative solutions or features you've considered.
+- type: textarea
+ attributes:
+ label: Additional Information
+ description: Add any other context about the problem here.
diff --git a/.github/PULL_REQUEST_TEMPLATES.md b/.github/PULL_REQUEST_TEMPLATES.md
new file mode 100644
index 0000000..fb6ea68
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATES.md
@@ -0,0 +1,20 @@
+
+
+**Overview**:
+
+
+**What problem does it solve?**:
+
+
+**Special notes for a reviewer**:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2f85879
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+version: 2
+
+updates:
+ - package-ecosystem: gomod
+ directory: /
+ labels:
+ - area/dependencies
+ schedule:
+ interval: daily
+
+ - package-ecosystem: github-actions
+ directory: /
+ labels:
+ - area/dependencies
+ schedule:
+ interval: daily
diff --git a/.github/logo.png b/.github/logo.png
new file mode 100644
index 0000000..1064268
Binary files /dev/null and b/.github/logo.png differ
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..e8353e8
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,30 @@
+changelog:
+ exclude:
+ labels:
+ - release-note/ignore
+ categories:
+ - title: Exciting New Features π
+ labels:
+ - kind/feature
+ - release-note/new-feature
+ - title: Enhancements π
+ labels:
+ - kind/enhancement
+ - release-note/enhancement
+ - title: Bug Fixes π
+ labels:
+ - kind/bug
+ - release-note/bug-fix
+ - title: Breaking Changes π
+ labels:
+ - release-note/breaking-change
+ - title: Deprecations β
+ labels:
+ - release-note/deprecation
+ - title: Dependency Updates β¬οΈ
+ labels:
+ - area/dependencies
+ - release-note/dependency-update
+ - title: Other Changes
+ labels:
+ - "*"
diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig
deleted file mode 100644
index 7bd3346..0000000
--- a/.github/workflows/.editorconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-[*.yml]
-indent_size = 2
diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml
new file mode 100644
index 0000000..62e989d
--- /dev/null
+++ b/.github/workflows/checks.yaml
@@ -0,0 +1,18 @@
+name: PR Checks
+
+on:
+ pull_request:
+ types: [opened, labeled, unlabeled, synchronize]
+
+jobs:
+ release-label:
+ name: Release note label
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check minimum labels
+ uses: mheap/github-action-required-labels@v3
+ with:
+ mode: minimum
+ count: 1
+ labels: "release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..acdb0ba
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,85 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - goos: js
+ goarch: wasm
+ - goos: aix
+ goarch: ppc64
+
+ steps:
+ - name: Set up Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.19
+
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Build
+ run: go build .
+ env:
+ GOOS: ${{ matrix.goos }}
+ GOARCH: ${{ matrix.goarch }}
+
+ test:
+ name: Test
+ runs-on: ${{ matrix.os }}
+ strategy:
+ # Fail fast is disabled because there are Go version specific features and tests
+ # that should be able to fail independently.
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ go: ['1.17', '1.18', '1.19']
+ env:
+ GOFLAGS: -mod=readonly
+
+ steps:
+ - name: Set up Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: ${{ matrix.go }}
+
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Test
+ run: go test -race -v ./...
+ if: runner.os != 'Windows'
+
+ - name: Test (without race detector)
+ run: go test -v ./...
+ if: runner.os == 'Windows'
+
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ env:
+ GOFLAGS: -mod=readonly
+
+ steps:
+ - name: Set up Go
+ uses: actions/setup-go@v3
+ with:
+ go-version: 1.19
+
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Lint
+ uses: golangci/golangci-lint-action@v3
+ with:
+ version: v1.50.1
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 576e740..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: CI
-
-on:
- push:
- branches:
- - master
- pull_request:
-
-jobs:
- build:
- name: Build
- runs-on: ubuntu-latest
- strategy:
- max-parallel: 10
- matrix:
- go: ['1.11', '1.12', '1.13']
- env:
- VERBOSE: 1
- GOFLAGS: -mod=readonly
- GOPROXY: https://proxy.golang.org
-
- steps:
- - name: Set up Go
- uses: actions/setup-go@v1
- with:
- go-version: ${{ matrix.go }}
-
- - name: Checkout code
- uses: actions/checkout@v1
-
- - name: Run tests
- run: make test
-
- - name: Run linter
- run: make lint
diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml
new file mode 100644
index 0000000..b64f147
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yaml
@@ -0,0 +1,72 @@
+# 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.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ master ]
+ schedule:
+ - cron: '22 16 * * 3'
+
+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@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ 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@v2
+
+ # βΉοΈ 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@v2
+
diff --git a/.github/workflows/feedback_issue.yaml b/.github/workflows/feedback_issue.yaml
new file mode 100644
index 0000000..3859624
--- /dev/null
+++ b/.github/workflows/feedback_issue.yaml
@@ -0,0 +1,32 @@
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ comment:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/github-script@v6
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: `π Thanks for reporting!
+
+ A maintainer will take a look at your issue shortly. π
+
+ In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.
+
+ β° If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9
+
+ π£ If you've already given us your feedback, you can still help by spreading the news,
+ either by sharing the above link or telling people about this on Twitter:
+
+ https://twitter.com/sagikazarmark/status/1306904078967074816
+
+ **Thank you!** β€οΈ
+ `,
+ })
diff --git a/.github/workflows/feedback_pull_request.yaml b/.github/workflows/feedback_pull_request.yaml
new file mode 100644
index 0000000..f5f6ab4
--- /dev/null
+++ b/.github/workflows/feedback_pull_request.yaml
@@ -0,0 +1,32 @@
+on:
+ pull_request_target:
+ types: [opened]
+
+jobs:
+ comment:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/github-script@v6
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: `π Thanks for contributing to Viper! You are awesome! π
+
+ A maintainer will take a look at your pull request shortly. π
+
+ In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.
+
+ β° If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9
+
+ π£ If you've already given us your feedback, you can still help by spreading the news,
+ either by sharing the above link or telling people about this on Twitter:
+
+ https://twitter.com/sagikazarmark/status/1306904078967074816
+
+ **Thank you!** β€οΈ
+ `,
+ })
diff --git a/.gitignore b/.gitignore
index d6941f3..8962508 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,5 @@
+/.idea/
/bin/
/build/
/var/
/vendor/
-
-# IDE integration
-/.vscode/*
-!/.vscode/launch.json
-!/.vscode/tasks.json
-/.idea/*
-!/.idea/codeStyles/
-!/.idea/copyright/
-!/.idea/dataSources.xml
-!/.idea/*.iml
-!/.idea/externalDependencies.xml
-!/.idea/go.imports.xml
-!/.idea/modules.xml
-!/.idea/runConfigurations/
-!/.idea/scopes/
-!/.idea/sqldialects.xml
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 0000000..16e0396
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,96 @@
+run:
+ timeout: 5m
+
+linters-settings:
+ gci:
+ sections:
+ - standard
+ - default
+ - prefix(github.com/spf13/viper)
+ golint:
+ min-confidence: 0
+ goimports:
+ local-prefixes: github.com/spf13/viper
+
+linters:
+ disable-all: true
+ enable:
+ - bodyclose
+ - deadcode
+ - dogsled
+ - dupl
+ - durationcheck
+ - exhaustive
+ - exportloopref
+ - gci
+ - gofmt
+ - gofumpt
+ - goimports
+ - gomoddirectives
+ - goprintffuncname
+ - govet
+ - importas
+ - ineffassign
+ - makezero
+ - misspell
+ - nakedret
+ - nilerr
+ - noctx
+ - nolintlint
+ - prealloc
+ - predeclared
+ - revive
+ - rowserrcheck
+ - sqlclosecheck
+ - staticcheck
+ - structcheck
+ - stylecheck
+ - tparallel
+ - typecheck
+ - unconvert
+ - unparam
+ - unused
+ - varcheck
+ - wastedassign
+ - whitespace
+
+ # fixme
+ # - cyclop
+ # - errcheck
+ # - errorlint
+ # - exhaustivestruct
+ # - forbidigo
+ # - forcetypeassert
+ # - gochecknoglobals
+ # - gochecknoinits
+ # - gocognit
+ # - goconst
+ # - gocritic
+ # - gocyclo
+ # - godot
+ # - gosec
+ # - gosimple
+ # - ifshort
+ # - lll
+ # - nlreturn
+ # - paralleltest
+ # - scopelint
+ # - thelper
+ # - wrapcheck
+
+ # unused
+ # - depguard
+ # - goheader
+ # - gomodguard
+
+ # don't enable:
+ # - asciicheck
+ # - funlen
+ # - godox
+ # - goerr113
+ # - gomnd
+ # - interfacer
+ # - maligned
+ # - nestif
+ # - testpackage
+ # - wsl
diff --git a/.golangci.yml b/.golangci.yml
deleted file mode 100644
index 0ea9249..0000000
--- a/.golangci.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-linters-settings:
- golint:
- min-confidence: 0.1
- goimports:
- local-prefixes: github.com/spf13/viper
-
-linters:
- enable-all: true
- disable:
- - funlen
- - maligned
-
- # TODO: fix me
- - wsl
- - gochecknoinits
- - gosimple
- - gochecknoglobals
- - errcheck
- - lll
- - godox
- - scopelint
- - gocyclo
- - gocognit
- - gocritic
\ No newline at end of file
diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml
deleted file mode 100644
index 3ee9345..0000000
--- a/.idea/externalDependencies.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
deleted file mode 100644
index 560a90c..0000000
--- a/.idea/go.imports.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 1a6ea8e..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Check.xml b/.idea/runConfigurations/Check.xml
deleted file mode 100644
index 8477e06..0000000
--- a/.idea/runConfigurations/Check.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Lint.xml b/.idea/runConfigurations/Lint.xml
deleted file mode 100644
index 45aa432..0000000
--- a/.idea/runConfigurations/Lint.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/Tests.xml b/.idea/runConfigurations/Tests.xml
deleted file mode 100644
index f5b3013..0000000
--- a/.idea/runConfigurations/Tests.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/viper.iml b/.idea/viper.iml
deleted file mode 100644
index 5e764c4..0000000
--- a/.idea/viper.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Makefile b/Makefile
index e39b8b5..3f4234d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,12 @@
# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
OS = $(shell uname | tr A-Z a-z)
+export PATH := $(abspath bin/):${PATH}
# Build variables
BUILD_DIR ?= build
+export CGO_ENABLED ?= 0
+export GOOS = $(shell go env GOOS)
ifeq (${VERBOSE}, 1)
ifeq ($(filter -v,${GOARGS}),)
GOARGS += -v
@@ -12,8 +15,8 @@ TEST_FORMAT = short-verbose
endif
# Dependency versions
-GOTESTSUM_VERSION = 0.3.5
-GOLANGCI_VERSION = 1.21.0
+GOTESTSUM_VERSION = 1.8.0
+GOLANGCI_VERSION = 1.50.1
# Add the ability to override some variables
# Use with care
@@ -33,21 +36,20 @@ bin/gotestsum-${GOTESTSUM_VERSION}:
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum-${GOTESTSUM_VERSION} && chmod +x ./bin/gotestsum-${GOTESTSUM_VERSION}
TEST_PKGS ?= ./...
-TEST_REPORT_NAME ?= results.xml
.PHONY: test
-test: TEST_REPORT ?= main
test: TEST_FORMAT ?= short
test: SHELL = /bin/bash
+test: export CGO_ENABLED=1
test: bin/gotestsum ## Run tests
- @mkdir -p ${BUILD_DIR}/test_results/${TEST_REPORT}
- bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/test_results/${TEST_REPORT}/${TEST_REPORT_NAME} --format ${TEST_FORMAT} -- $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
+ @mkdir -p ${BUILD_DIR}
+ bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
bin/golangci-lint-${GOLANGCI_VERSION}:
@mkdir -p bin
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
- @mv bin/golangci-lint $@
+ curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}
+ @mv bin/golangci-lint "$@"
.PHONY: lint
lint: bin/golangci-lint ## Run linter
@@ -57,6 +59,9 @@ lint: bin/golangci-lint ## Run linter
fix: bin/golangci-lint ## Fix lint violations
bin/golangci-lint run --fix
+# Add custom targets here
+-include custom.mk
+
.PHONY: list
list: ## List all make targets
@${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort
diff --git a/README.md b/README.md
index 2bb54fa..cd39290 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,20 @@
-
+> ## Viper v2 feedback
+> Viper is heading towards v2 and we would love to hear what _**you**_ would like to see in it. Share your thoughts here: https://forms.gle/R6faU74qPRPAzchZ9
+>
+> **Thank you!**
-Go configuration with fangs!
+
-[](https://github.com/spf13/viper)
+[](https://github.com/avelino/awesome-go#configuration)
+[](https://repl.it/@sagikazarmark/Viper-example#main.go)
+
+[](https://github.com/spf13/viper/actions?query=workflow%3ACI)
[](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](https://godoc.org/github.com/spf13/viper)
+[](https://goreportcard.com/report/github.com/spf13/viper)
+
+[](https://pkg.go.dev/mod/github.com/spf13/viper)
+
+**Go configuration with fangs!**
Many Go projects are built using Viper including:
@@ -21,15 +31,17 @@ Many Go projects are built using Viper including:
## Install
-```console
+```shell
go get github.com/spf13/viper
```
+**Note:** Viper uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
+
## What is Viper?
-Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed
-to work within an application, and can handle all types of configuration needs
+Viper is a complete configuration solution for Go applications including [12-Factor apps](https://12factor.net/#the_twelve_factors).
+It is designed to work within an application, and can handle all types of configuration needs
and formats. It supports:
* setting defaults
@@ -101,12 +113,13 @@ where a configuration file is expected.
```go
viper.SetConfigName("config") // name of config file (without extension)
+viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
- panic(fmt.Errorf("Fatal error config file: %s \n", err))
+ panic(fmt.Errorf("fatal error config file: %w", err))
}
```
@@ -114,17 +127,17 @@ You can handle the specific case where no config file is found like this:
```go
if err := viper.ReadInConfig(); err != nil {
- if _, ok := err.(viper.ConfigFileNotFoundError); ok {
- // Config file not found; ignore error if desired
- } else {
- // Config file was found but another error was produced
- }
+ if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+ // Config file not found; ignore error if desired
+ } else {
+ // Config file was found but another error was produced
+ }
}
// Config file found and successfully parsed
```
-*NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
+*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
### Writing Config Files
@@ -162,10 +175,10 @@ Optionally you can provide a function for Viper to run each time a change occurs
**Make sure you add all of the configPaths prior to calling `WatchConfig()`**
```go
-viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
+viper.WatchConfig()
```
### Reading Config from io.Reader
@@ -241,9 +254,10 @@ using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from
the environment variables. Both `BindEnv` and `AutomaticEnv` will use this
prefix.
-`BindEnv` takes one or two parameters. The first parameter is the key name, the
-second is the name of the environment variable. The name of the environment
-variable is case sensitive. If the ENV variable name is not provided, then
+`BindEnv` takes one or more parameters. The first parameter is the key name, the
+rest are the name of the environment variables to bind to this key. If more than
+one are provided, they will take precedence in the specified order. The name of
+the environment variable is case sensitive. If the ENV variable name is not provided, then
Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter),
it **does not** automatically add the prefix. For example if the second parameter is "id",
Viper will look for the ENV variable "ID".
@@ -255,7 +269,7 @@ the `BindEnv` is called.
`AutomaticEnv` is a powerful helper especially when combined with
`SetEnvPrefix`. When called, Viper will check for an environment variable any
time a `viper.Get` request is made. It will apply the following rules. It will
-check for a environment variable with a name matching the key uppercased and
+check for an environment variable with a name matching the key uppercased and
prefixed with the `EnvPrefix` if set.
`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env
@@ -340,7 +354,7 @@ func main() {
i := viper.GetInt("flagname") // retrieve value from viper
- ...
+ // ...
}
```
@@ -399,7 +413,7 @@ in a Key/Value store such as etcd or Consul. These values take precedence over
default values, but are overridden by configuration values retrieved from disk,
flags, or environment variables.
-Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve
+Viper uses [crypt](https://github.com/bketelsen/crypt) to retrieve
configuration from the K/V store, which means that you can store your
configuration values encrypted and have them automatically decrypted if you have
the correct gpg keyring. Encryption is optional.
@@ -411,7 +425,7 @@ independently of it.
K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001.
```bash
-$ go get github.com/xordataexchange/crypt/bin/crypt
+$ go get github.com/bketelsen/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
```
@@ -433,8 +447,15 @@ viper.SetConfigType("json") // because there is no file extension in a stream of
err := viper.ReadRemoteConfig()
```
+#### etcd3
+```go
+viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
+viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
+err := viper.ReadRemoteConfig()
+```
+
#### Consul
-You need to set a key to Consul key/value storage with JSON value containing your desired config.
+You need to set a key to Consul key/value storage with JSON value containing your desired config.
For example, create a Consul key/value store key `MY_CONSUL_KEY` with value:
```json
@@ -453,6 +474,16 @@ fmt.Println(viper.Get("port")) // 8080
fmt.Println(viper.Get("hostname")) // myhostname.com
```
+#### Firestore
+
+```go
+viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
+viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml"
+err := viper.ReadRemoteConfig()
+```
+
+Of course, you're allowed to use `SecureRemoteProvider` also
+
### Remote Key/Value Store Example - Encrypted
```go
@@ -479,18 +510,18 @@ runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to watch remote changes forever
go func(){
for {
- time.Sleep(time.Second * 5) // delay after each request
+ time.Sleep(time.Second * 5) // delay after each request
- // currently, only tested with etcd support
- err := runtime_viper.WatchRemoteConfig()
- if err != nil {
- log.Errorf("unable to read remote config: %v", err)
- continue
- }
+ // currently, only tested with etcd support
+ err := runtime_viper.WatchRemoteConfig()
+ if err != nil {
+ log.Errorf("unable to read remote config: %v", err)
+ continue
+ }
- // unmarshal new config into our runtime config struct. you can also use channel
- // to implement a signal to notify the system of the changes
- runtime_viper.Unmarshal(&runtime_conf)
+ // unmarshal new config into our runtime config struct. you can also use channel
+ // to implement a signal to notify the system of the changes
+ runtime_viper.Unmarshal(&runtime_conf)
}
}()
```
@@ -522,7 +553,7 @@ Example:
```go
viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
- fmt.Println("verbose enabled")
+ fmt.Println("verbose enabled")
}
```
### Accessing nested keys
@@ -568,10 +599,37 @@ the `Set()` method, β¦) with an immediate value, then all sub-keys of
`datastore.metric` become undefined, they are βshadowedβ by the higher-priority
configuration level.
+Viper can access array indices by using numbers in the path. For example:
+
+```jsonc
+{
+ "host": {
+ "address": "localhost",
+ "ports": [
+ 5799,
+ 6029
+ ]
+ },
+ "datastore": {
+ "metric": {
+ "host": "127.0.0.1",
+ "port": 3099
+ },
+ "warehouse": {
+ "host": "198.0.0.1",
+ "port": 2112
+ }
+ }
+}
+
+GetInt("host.ports.1") // returns 6029
+
+```
+
Lastly, if there exists a key that matches the delimited key path, its value
will be returned instead. E.g.
-```json
+```jsonc
{
"datastore.metric.host": "0.0.0.0",
"host": {
@@ -593,14 +651,15 @@ will be returned instead. E.g.
GetString("datastore.metric.host") // returns "0.0.0.0"
```
-### Extract sub-tree
+### Extracting a sub-tree
-Extract sub-tree from Viper.
+When developing reusable modules, it's often useful to extract a subset of the configuration
+and pass it to a module. This way the module can be instantiated more than once, with different configurations.
-For example, `viper` represents:
+For example, an application might use multiple different cache stores for different purposes:
-```json
-app:
+```yaml
+cache:
cache1:
max-items: 100
item-size: 64
@@ -609,35 +668,36 @@ app:
item-size: 80
```
-After executing:
+We could pass the cache name to a module (eg. `NewCache("cache1")`),
+but it would require weird concatenation for accessing config keys and would be less separated from the global config.
+
+So instead of doing that let's pass a Viper instance to the constructor that represents a subset of the configuration:
```go
-subv := viper.Sub("app.cache1")
+cache1Config := viper.Sub("cache.cache1")
+if cache1Config == nil { // Sub returns nil if the key cannot be found
+ panic("cache configuration not found")
+}
+
+cache1 := NewCache(cache1Config)
```
-`subv` represents:
+**Note:** Always check the return value of `Sub`. It returns `nil` if a key cannot be found.
-```json
-max-items: 100
-item-size: 64
-```
-
-Suppose we have:
+Internally, the `NewCache` function can address `max-items` and `item-size` keys directly:
```go
-func NewCache(cfg *Viper) *Cache {...}
+func NewCache(v *Viper) *Cache {
+ return &Cache{
+ MaxItems: v.GetInt("max-items"),
+ ItemSize: v.GetInt("item-size"),
+ }
+}
```
-which creates a cache based on config information formatted as `subv`.
-Now itβs easy to create these 2 caches separately as:
+The resulting code is easy to test, since it's decoupled from the main config structure,
+and easier to reuse (for the same reason).
-```go
-cfg1 := viper.Sub("app.cache1")
-cache1 := NewCache(cfg1)
-
-cfg2 := viper.Sub("app.cache2")
-cache2 := NewCache(cfg2)
-```
### Unmarshaling
@@ -673,18 +733,18 @@ you have to change the delimiter:
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.SetDefault("chart::values", map[string]interface{}{
- "ingress": map[string]interface{}{
- "annotations": map[string]interface{}{
- "traefik.frontend.rule.type": "PathPrefix",
- "traefik.ingress.kubernetes.io/ssl-redirect": "true",
- },
- },
+ "ingress": map[string]interface{}{
+ "annotations": map[string]interface{}{
+ "traefik.frontend.rule.type": "PathPrefix",
+ "traefik.ingress.kubernetes.io/ssl-redirect": "true",
+ },
+ },
})
type config struct {
Chart struct{
- Values map[string]interface{}
- }
+ Values map[string]interface{}
+ }
}
var C config
@@ -725,24 +785,33 @@ if err != nil {
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
+### Decoding custom formats
+
+A frequently requested feature for Viper is adding more value formats and decoders.
+For example, parsing character (dot, comma, semicolon, etc) separated strings into slices.
+
+This is already available in Viper using mapstructure decode hooks.
+
+Read more about the details in [this blog post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/).
+
### Marshalling to string
-You may need to marshal all the settings held in viper into a string rather than write them to a file.
+You may need to marshal all the settings held in viper into a string rather than write them to a file.
You can use your favorite format's marshaller with the config returned by `AllSettings()`.
```go
import (
- yaml "gopkg.in/yaml.v2"
- // ...
-)
+ yaml "gopkg.in/yaml.v2"
+ // ...
+)
func yamlStringSettings() string {
- c := viper.AllSettings()
- bs, err := yaml.Marshal(c)
- if err != nil {
- log.Fatalf("unable to marshal config to YAML: %v", err)
- }
- return string(bs)
+ c := viper.AllSettings()
+ bs, err := yaml.Marshal(c)
+ if err != nil {
+ log.Fatalf("unable to marshal config to YAML: %v", err)
+ }
+ return string(bs)
}
```
@@ -778,15 +847,35 @@ y.SetDefault("ContentDir", "foobar")
When working with multiple vipers, it is up to the user to keep track of the
different vipers.
+
## Q & A
-Q: Why is it called βViperβ?
+### Why is it called βViperβ?
A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))
to [Cobra](https://github.com/spf13/cobra). While both can operate completely
independently, together they make a powerful pair to handle much of your
application foundation needs.
-Q: Why is it called βCobraβ?
+### Why is it called βCobraβ?
-A: Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?
+Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?
+
+### Does Viper support case sensitive keys?
+
+**tl;dr:** No.
+
+Viper merges configuration from various sources, many of which are either case insensitive or uses different casing than the rest of the sources (eg. env vars).
+In order to provide the best experience when using multiple sources, the decision has been made to make all keys case insensitive.
+
+There has been several attempts to implement case sensitivity, but unfortunately it's not that trivial. We might take a stab at implementing it in [Viper v2](https://github.com/spf13/viper/issues/772), but despite the initial noise, it does not seem to be requested that much.
+
+You can vote for case sensitivity by filling out this feedback form: https://forms.gle/R6faU74qPRPAzchZ9
+
+### Is it safe to concurrently read and write to a viper?
+
+No, you will need to synchronize access to the viper yourself (for example by using the `sync` package). Concurrent reads and writes can cause a panic.
+
+## Troubleshooting
+
+See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
new file mode 100644
index 0000000..c4e36c6
--- /dev/null
+++ b/TROUBLESHOOTING.md
@@ -0,0 +1,32 @@
+# Troubleshooting
+
+## Unmarshaling doesn't work
+
+The most common reason for this issue is improper use of struct tags (eg. `yaml` or `json`). Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. Please refer to the library's documentation for using other struct tags.
+
+## Cannot find package
+
+Viper installation seems to fail a lot lately with the following (or a similar) error:
+
+```
+cannot find package "github.com/hashicorp/hcl/tree/hcl1" in any of:
+/usr/local/Cellar/go/1.15.7_1/libexec/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOROOT)
+/Users/user/go/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOPATH)
+```
+
+As the error message suggests, Go tries to look up dependencies in `GOPATH` mode (as it's commonly called) from the `GOPATH`.
+Viper opted to use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage its dependencies. While in many cases the two methods are interchangeable, once a dependency releases new (major) versions, `GOPATH` mode is no longer able to decide which version to use, so it'll either use one that's already present or pick a version (usually the `master` branch).
+
+The solution is easy: switch to using Go Modules.
+Please refer to the [wiki](https://github.com/golang/go/wiki/Modules) on how to do that.
+
+**tl;dr* `export GO111MODULE=on`
+
+## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file
+
+This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740).
+
+Potential solutions are:
+
+1. Quoting values resolved as boolean
+1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build)
diff --git a/experimental_logger.go b/experimental_logger.go
new file mode 100644
index 0000000..206dad6
--- /dev/null
+++ b/experimental_logger.go
@@ -0,0 +1,11 @@
+//go:build viper_logger
+// +build viper_logger
+
+package viper
+
+// WithLogger sets a custom logger.
+func WithLogger(l Logger) Option {
+ return optionFunc(func(v *Viper) {
+ v.logger = l
+ })
+}
diff --git a/flags_test.go b/flags_test.go
index 0b976b6..a22e31b 100644
--- a/flags_test.go
+++ b/flags_test.go
@@ -10,13 +10,13 @@ import (
func TestBindFlagValueSet(t *testing.T) {
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
- var testValues = map[string]*string{
+ testValues := map[string]*string{
"host": nil,
"port": nil,
"endpoint": nil,
}
- var mutatedTestValues = map[string]string{
+ mutatedTestValues := map[string]string{
"host": "localhost",
"port": "6060",
"endpoint": "/public",
@@ -44,8 +44,8 @@ func TestBindFlagValueSet(t *testing.T) {
}
func TestBindFlagValue(t *testing.T) {
- var testString = "testing"
- var testValue = newStringValue(testString, &testString)
+ testString := "testing"
+ testValue := newStringValue(testString, &testString)
flag := &pflag.Flag{
Name: "testflag",
@@ -59,7 +59,7 @@ func TestBindFlagValue(t *testing.T) {
assert.Equal(t, testString, Get("testvalue"))
flag.Value.Set("testing_mutate")
- flag.Changed = true //hack for pflag usage
+ flag.Changed = true // hack for pflag usage
assert.Equal(t, "testing_mutate", Get("testvalue"))
}
diff --git a/fs.go b/fs.go
new file mode 100644
index 0000000..ecb1769
--- /dev/null
+++ b/fs.go
@@ -0,0 +1,65 @@
+//go:build go1.16 && finder
+// +build go1.16,finder
+
+package viper
+
+import (
+ "errors"
+ "io/fs"
+ "path"
+)
+
+type finder struct {
+ paths []string
+ fileNames []string
+ extensions []string
+
+ withoutExtension bool
+}
+
+func (f finder) Find(fsys fs.FS) (string, error) {
+ for _, searchPath := range f.paths {
+ for _, fileName := range f.fileNames {
+ for _, extension := range f.extensions {
+ filePath := path.Join(searchPath, fileName+"."+extension)
+
+ ok, err := fileExists(fsys, filePath)
+ if err != nil {
+ return "", err
+ }
+
+ if ok {
+ return filePath, nil
+ }
+ }
+
+ if f.withoutExtension {
+ filePath := path.Join(searchPath, fileName)
+
+ ok, err := fileExists(fsys, filePath)
+ if err != nil {
+ return "", err
+ }
+
+ if ok {
+ return filePath, nil
+ }
+ }
+ }
+ }
+
+ return "", nil
+}
+
+func fileExists(fsys fs.FS, filePath string) (bool, error) {
+ fileInfo, err := fs.Stat(fsys, filePath)
+ if err == nil {
+ return !fileInfo.IsDir(), nil
+ }
+
+ if errors.Is(err, fs.ErrNotExist) {
+ return false, nil
+ }
+
+ return false, err
+}
diff --git a/fs_test.go b/fs_test.go
new file mode 100644
index 0000000..071e346
--- /dev/null
+++ b/fs_test.go
@@ -0,0 +1,100 @@
+//go:build go1.16 && finder
+// +build go1.16,finder
+
+package viper
+
+import (
+ "io/fs"
+ "testing"
+ "testing/fstest"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFinder(t *testing.T) {
+ t.Parallel()
+
+ fsys := fstest.MapFS{
+ "home/user/.config": &fstest.MapFile{},
+ "home/user/config.json": &fstest.MapFile{},
+ "home/user/config.yaml": &fstest.MapFile{},
+ "home/user/data.json": &fstest.MapFile{},
+ "etc/config/.config": &fstest.MapFile{},
+ "etc/config/a_random_file.txt": &fstest.MapFile{},
+ "etc/config/config.json": &fstest.MapFile{},
+ "etc/config/config.yaml": &fstest.MapFile{},
+ "etc/config/config.xml": &fstest.MapFile{},
+ }
+
+ testCases := []struct {
+ name string
+ fsys func() fs.FS
+ finder finder
+ result string
+ }{
+ {
+ name: "find file",
+ fsys: func() fs.FS { return fsys },
+ finder: finder{
+ paths: []string{"etc/config"},
+ fileNames: []string{"config"},
+ extensions: []string{"json"},
+ },
+ result: "etc/config/config.json",
+ },
+ {
+ name: "file not found",
+ fsys: func() fs.FS { return fsys },
+ finder: finder{
+ paths: []string{"var/config"},
+ fileNames: []string{"config"},
+ extensions: []string{"json"},
+ },
+ result: "",
+ },
+ {
+ name: "empty search params",
+ fsys: func() fs.FS { return fsys },
+ finder: finder{},
+ result: "",
+ },
+ {
+ name: "precedence",
+ fsys: func() fs.FS { return fsys },
+ finder: finder{
+ paths: []string{"var/config", "home/user", "etc/config"},
+ fileNames: []string{"aconfig", "config"},
+ extensions: []string{"zml", "xml", "json"},
+ },
+ result: "home/user/config.json",
+ },
+ {
+ name: "without extension",
+ fsys: func() fs.FS { return fsys },
+ finder: finder{
+ paths: []string{"var/config", "home/user", "etc/config"},
+ fileNames: []string{".config"},
+ extensions: []string{"zml", "xml", "json"},
+
+ withoutExtension: true,
+ },
+ result: "home/user/.config",
+ },
+ }
+
+ for _, testCase := range testCases {
+ testCase := testCase
+
+ t.Run(testCase.name, func(t *testing.T) {
+ t.Parallel()
+
+ fsys := testCase.fsys()
+
+ result, err := testCase.finder.Find(fsys)
+ require.NoError(t, err)
+
+ assert.Equal(t, testCase.result, result)
+ })
+ }
+}
diff --git a/go.mod b/go.mod
index db7724b..d01ea3f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,48 +1,75 @@
-module github.com/ShaleApps/viper
+module github.com/spf13/viper
-go 1.12
+go 1.17
require (
- github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
- github.com/coreos/bbolt v1.3.2 // indirect
- github.com/coreos/etcd v3.3.10+incompatible // indirect
- github.com/coreos/go-semver v0.2.0 // indirect
- github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
- github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
- github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
- github.com/fsnotify/fsnotify v1.4.7
- github.com/gogo/protobuf v1.2.1 // indirect
- github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
- github.com/google/btree v1.0.0 // indirect
- github.com/gorilla/websocket v1.4.0 // indirect
- github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
- github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
- github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect
+ github.com/fsnotify/fsnotify v1.6.0
github.com/hashicorp/hcl v1.0.0
- github.com/jonboulle/clockwork v0.1.0 // indirect
- github.com/magiconair/properties v1.8.1
- github.com/mitchellh/mapstructure v1.1.2
- github.com/pelletier/go-toml v1.6.0
- github.com/prometheus/client_golang v0.9.3 // indirect
- github.com/smartystreets/goconvey v1.6.4 // indirect
- github.com/soheilhy/cmux v0.1.4 // indirect
- github.com/spf13/afero v1.2.2
- github.com/spf13/cast v1.3.1
+ github.com/magiconair/properties v1.8.7
+ github.com/mitchellh/mapstructure v1.5.0
+ github.com/pelletier/go-toml/v2 v2.0.6
+ github.com/sagikazarmark/crypt v0.9.0
+ github.com/spf13/afero v1.9.3
+ github.com/spf13/cast v1.5.0
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.4.0
- github.com/subosito/gotenv v1.2.0
- github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
- github.com/ugorji/go v1.1.4 // indirect
- github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
- github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77
- go.etcd.io/bbolt v1.3.2 // indirect
- go.uber.org/atomic v1.4.0 // indirect
- go.uber.org/multierr v1.1.0 // indirect
- go.uber.org/zap v1.10.0 // indirect
- golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
- golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
- google.golang.org/grpc v1.21.0 // indirect
- gopkg.in/ini.v1 v1.51.1
- gopkg.in/yaml.v2 v2.2.7
+ github.com/stretchr/testify v1.8.1
+ github.com/subosito/gotenv v1.4.2
+ gopkg.in/ini.v1 v1.67.0
+ gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+ cloud.google.com/go v0.105.0 // indirect
+ cloud.google.com/go/compute v1.14.0 // indirect
+ cloud.google.com/go/compute/metadata v0.2.3 // indirect
+ cloud.google.com/go/firestore v1.9.0 // indirect
+ cloud.google.com/go/longrunning v0.3.0 // indirect
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/coreos/go-semver v0.3.0 // indirect
+ github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
+ github.com/googleapis/gax-go/v2 v2.7.0 // indirect
+ github.com/hashicorp/consul/api v1.18.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ go.etcd.io/etcd/api/v3 v3.5.6 // indirect
+ go.etcd.io/etcd/client/pkg/v3 v3.5.6 // indirect
+ go.etcd.io/etcd/client/v2 v2.305.6 // indirect
+ go.etcd.io/etcd/client/v3 v3.5.6 // indirect
+ go.opencensus.io v0.24.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.8.0 // indirect
+ go.uber.org/zap v1.21.0 // indirect
+ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
+ golang.org/x/net v0.4.0 // indirect
+ golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
+ golang.org/x/sync v0.1.0 // indirect
+ golang.org/x/sys v0.3.0 // indirect
+ golang.org/x/text v0.5.0 // indirect
+ golang.org/x/time v0.1.0 // indirect
+ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
+ google.golang.org/api v0.107.0 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
+ google.golang.org/grpc v1.52.0 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
)
diff --git a/go.sum b/go.sum
index fd0a06d..1f0b4d3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,195 +1,1366 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go 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 v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
+cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
+cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
+cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
+cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=
+cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=
+cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=
+cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=
+cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=
+cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=
+cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=
+cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
+cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=
+cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=
+cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
+cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
+cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=
+cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=
+cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=
+cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=
+cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=
+cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=
+cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=
+cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=
+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/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=
+cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=
+cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=
+cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=
+cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=
+cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=
+cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=
+cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=
+cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=
+cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=
+cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=
+cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=
+cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
+cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
+cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
+cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
+cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
+cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
+cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=
+cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=
+cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=
+cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=
+cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=
+cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=
+cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=
+cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
+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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=
+cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=
+cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=
+cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=
+cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=
+cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=
+cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=
+cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=
+cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
+cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
+cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=
+cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=
+cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=
+cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=
+cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=
+cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=
+cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=
+cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=
+cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=
+cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=
+cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=
+cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=
+cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=
+cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
+cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
+cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
+cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=
+cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
+cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=
+cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=
+cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=
+cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
+cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
+cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
+cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
+cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
+cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
+cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=
+cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
+cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=
+cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=
+cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=
+cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=
+cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
+cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=
+cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=
+cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=
+cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=
+cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=
+cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=
+cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=
+cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=
+cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=
+cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=
+cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=
+cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=
+cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
+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/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=
+cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
+cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=
+cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=
+cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=
+cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=
+cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=
+cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=
+cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=
+cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=
+cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=
+cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=
+cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=
+cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=
+cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=
+cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=
+cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=
+cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=
+cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=
+cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=
+cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=
+cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=
+cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=
+cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=
+cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=
+cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=
+cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=
+cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
+cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
+cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
+cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
+cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=
+cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=
+cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=
+cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=
+cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=
+cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=
+cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=
+cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
+cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
+cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
+cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=
+cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=
+cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=
+cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=
+cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=
+cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=
+cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=
+cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=
+cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=
+cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=
+cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=
+cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
+cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
+cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
+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/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+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-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+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/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+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/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
+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/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+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-20201218002935-b9804c9f04c2/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/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
+github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
+github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+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/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
+github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
+github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
+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 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+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-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
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.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+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-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+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-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+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.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
-github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
-github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sagikazarmark/crypt v0.9.0 h1:fipzMFW34hFUEc4D7fsLQFtE7yElkpgyS2zruedRdZk=
+github.com/sagikazarmark/crypt v0.9.0/go.mod h1:RnH7sEhxfdnPm1z+XMgSLjWTEIjyK4z2dw6+4vHTMuo=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
-github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
-github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
-github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
+github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+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/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
+github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+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=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A=
+go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8=
+go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU=
+go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
+go.etcd.io/etcd/client/v2 v2.305.6 h1:fIDR0p4KMjw01MJMfUIDWdQbjo06PD6CeYM5z4EHLi0=
+go.etcd.io/etcd/client/v2 v2.305.6/go.mod h1:BHha8XJGe8vCIBfWBpbBLVZ4QjOIlfoouvOwydu63E0=
+go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E=
+go.etcd.io/etcd/client/v3 v3.5.6/go.mod h1:f6GRinRMCsFVv9Ht42EyY7nfsVGwrNO0WEoS2pRKzQk=
+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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+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/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
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-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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-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-20201224014010-6772e930b67b/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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
+golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
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-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/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
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/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-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-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-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-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-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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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-20210423185535-09eb48e85fd7/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-20210603081109-ebe580a85c40/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+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/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
+golang.org/x/time v0.1.0/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-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-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.0.0-20210108195828-e2f9c7f1fc8e/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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+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=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+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.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.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
+google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
+google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
+google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU=
+google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
+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 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+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-20210108203827-ffc7fda8c3d7/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-20210226172003-ab064af71705/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-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+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/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
+google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
+google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
+google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
+google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+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 v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
+google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
+google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
+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=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
-gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+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.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
-gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/encoding/decoder.go b/internal/encoding/decoder.go
new file mode 100644
index 0000000..f472e9f
--- /dev/null
+++ b/internal/encoding/decoder.go
@@ -0,0 +1,61 @@
+package encoding
+
+import (
+ "sync"
+)
+
+// Decoder decodes the contents of b into v.
+// It's primarily used for decoding contents of a file into a map[string]interface{}.
+type Decoder interface {
+ Decode(b []byte, v map[string]interface{}) error
+}
+
+const (
+ // ErrDecoderNotFound is returned when there is no decoder registered for a format.
+ ErrDecoderNotFound = encodingError("decoder not found for this format")
+
+ // ErrDecoderFormatAlreadyRegistered is returned when an decoder is already registered for a format.
+ ErrDecoderFormatAlreadyRegistered = encodingError("decoder already registered for this format")
+)
+
+// DecoderRegistry can choose an appropriate Decoder based on the provided format.
+type DecoderRegistry struct {
+ decoders map[string]Decoder
+
+ mu sync.RWMutex
+}
+
+// NewDecoderRegistry returns a new, initialized DecoderRegistry.
+func NewDecoderRegistry() *DecoderRegistry {
+ return &DecoderRegistry{
+ decoders: make(map[string]Decoder),
+ }
+}
+
+// RegisterDecoder registers a Decoder for a format.
+// Registering a Decoder for an already existing format is not supported.
+func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+
+ if _, ok := e.decoders[format]; ok {
+ return ErrDecoderFormatAlreadyRegistered
+ }
+
+ e.decoders[format] = enc
+
+ return nil
+}
+
+// Decode calls the underlying Decoder based on the format.
+func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
+ e.mu.RLock()
+ decoder, ok := e.decoders[format]
+ e.mu.RUnlock()
+
+ if !ok {
+ return ErrDecoderNotFound
+ }
+
+ return decoder.Decode(b, v)
+}
diff --git a/internal/encoding/decoder_test.go b/internal/encoding/decoder_test.go
new file mode 100644
index 0000000..6cb56d0
--- /dev/null
+++ b/internal/encoding/decoder_test.go
@@ -0,0 +1,81 @@
+package encoding
+
+import (
+ "reflect"
+ "testing"
+)
+
+type decoder struct {
+ v map[string]interface{}
+}
+
+func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
+ for key, value := range d.v {
+ v[key] = value
+ }
+
+ return nil
+}
+
+func TestDecoderRegistry_RegisterDecoder(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ registry := NewDecoderRegistry()
+
+ err := registry.RegisterDecoder("myformat", decoder{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ t.Run("AlreadyRegistered", func(t *testing.T) {
+ registry := NewDecoderRegistry()
+
+ err := registry.RegisterDecoder("myformat", decoder{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = registry.RegisterDecoder("myformat", decoder{})
+ if err != ErrDecoderFormatAlreadyRegistered {
+ t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
+ }
+ })
+}
+
+func TestDecoderRegistry_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ registry := NewDecoderRegistry()
+ decoder := decoder{
+ v: map[string]interface{}{
+ "key": "value",
+ },
+ }
+
+ err := registry.RegisterDecoder("myformat", decoder)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ v := map[string]interface{}{}
+
+ err = registry.Decode("myformat", []byte("key: value"), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(decoder.v, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
+ }
+ })
+
+ t.Run("DecoderNotFound", func(t *testing.T) {
+ registry := NewDecoderRegistry()
+
+ v := map[string]interface{}{}
+
+ err := registry.Decode("myformat", nil, v)
+ if err != ErrDecoderNotFound {
+ t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
+ }
+ })
+}
diff --git a/internal/encoding/dotenv/codec.go b/internal/encoding/dotenv/codec.go
new file mode 100644
index 0000000..4485063
--- /dev/null
+++ b/internal/encoding/dotenv/codec.go
@@ -0,0 +1,61 @@
+package dotenv
+
+import (
+ "bytes"
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/subosito/gotenv"
+)
+
+const keyDelimiter = "_"
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables
+// (commonly called as dotenv format).
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ flattened := map[string]interface{}{}
+
+ flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
+
+ keys := make([]string, 0, len(flattened))
+
+ for key := range flattened {
+ keys = append(keys, key)
+ }
+
+ sort.Strings(keys)
+
+ var buf bytes.Buffer
+
+ for _, key := range keys {
+ _, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key]))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return buf.Bytes(), nil
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+ var buf bytes.Buffer
+
+ _, err := buf.Write(b)
+ if err != nil {
+ return err
+ }
+
+ env, err := gotenv.StrictParse(&buf)
+ if err != nil {
+ return err
+ }
+
+ for key, value := range env {
+ v[key] = value
+ }
+
+ return nil
+}
diff --git a/internal/encoding/dotenv/codec_test.go b/internal/encoding/dotenv/codec_test.go
new file mode 100644
index 0000000..d297071
--- /dev/null
+++ b/internal/encoding/dotenv/codec_test.go
@@ -0,0 +1,63 @@
+package dotenv
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+KEY=value
+`
+
+// encoded form of the data
+const encoded = `KEY=value
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "KEY": "value",
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(data, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/encoding/dotenv/map_utils.go b/internal/encoding/dotenv/map_utils.go
new file mode 100644
index 0000000..ce6e6ef
--- /dev/null
+++ b/internal/encoding/dotenv/map_utils.go
@@ -0,0 +1,41 @@
+package dotenv
+
+import (
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+ if shadow != nil && prefix != "" && shadow[prefix] != nil {
+ // prefix is shadowed => nothing more to flatten
+ return shadow
+ }
+ if shadow == nil {
+ shadow = make(map[string]interface{})
+ }
+
+ var m2 map[string]interface{}
+ if prefix != "" {
+ prefix += delimiter
+ }
+ for k, val := range m {
+ fullKey := prefix + k
+ switch val.(type) {
+ case map[string]interface{}:
+ m2 = val.(map[string]interface{})
+ case map[interface{}]interface{}:
+ m2 = cast.ToStringMap(val)
+ default:
+ // immediate value
+ shadow[strings.ToLower(fullKey)] = val
+ continue
+ }
+ // recursively merge to shadow map
+ shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+ }
+ return shadow
+}
diff --git a/internal/encoding/encoder.go b/internal/encoding/encoder.go
new file mode 100644
index 0000000..2341bf2
--- /dev/null
+++ b/internal/encoding/encoder.go
@@ -0,0 +1,60 @@
+package encoding
+
+import (
+ "sync"
+)
+
+// Encoder encodes the contents of v into a byte representation.
+// It's primarily used for encoding a map[string]interface{} into a file format.
+type Encoder interface {
+ Encode(v map[string]interface{}) ([]byte, error)
+}
+
+const (
+ // ErrEncoderNotFound is returned when there is no encoder registered for a format.
+ ErrEncoderNotFound = encodingError("encoder not found for this format")
+
+ // ErrEncoderFormatAlreadyRegistered is returned when an encoder is already registered for a format.
+ ErrEncoderFormatAlreadyRegistered = encodingError("encoder already registered for this format")
+)
+
+// EncoderRegistry can choose an appropriate Encoder based on the provided format.
+type EncoderRegistry struct {
+ encoders map[string]Encoder
+
+ mu sync.RWMutex
+}
+
+// NewEncoderRegistry returns a new, initialized EncoderRegistry.
+func NewEncoderRegistry() *EncoderRegistry {
+ return &EncoderRegistry{
+ encoders: make(map[string]Encoder),
+ }
+}
+
+// RegisterEncoder registers an Encoder for a format.
+// Registering a Encoder for an already existing format is not supported.
+func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+
+ if _, ok := e.encoders[format]; ok {
+ return ErrEncoderFormatAlreadyRegistered
+ }
+
+ e.encoders[format] = enc
+
+ return nil
+}
+
+func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
+ e.mu.RLock()
+ encoder, ok := e.encoders[format]
+ e.mu.RUnlock()
+
+ if !ok {
+ return nil, ErrEncoderNotFound
+ }
+
+ return encoder.Encode(v)
+}
diff --git a/internal/encoding/encoder_test.go b/internal/encoding/encoder_test.go
new file mode 100644
index 0000000..adee6d0
--- /dev/null
+++ b/internal/encoding/encoder_test.go
@@ -0,0 +1,70 @@
+package encoding
+
+import (
+ "testing"
+)
+
+type encoder struct {
+ b []byte
+}
+
+func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
+ return e.b, nil
+}
+
+func TestEncoderRegistry_RegisterEncoder(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ registry := NewEncoderRegistry()
+
+ err := registry.RegisterEncoder("myformat", encoder{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ t.Run("AlreadyRegistered", func(t *testing.T) {
+ registry := NewEncoderRegistry()
+
+ err := registry.RegisterEncoder("myformat", encoder{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = registry.RegisterEncoder("myformat", encoder{})
+ if err != ErrEncoderFormatAlreadyRegistered {
+ t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err)
+ }
+ })
+}
+
+func TestEncoderRegistry_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ registry := NewEncoderRegistry()
+ encoder := encoder{
+ b: []byte("key: value"),
+ }
+
+ err := registry.RegisterEncoder("myformat", encoder)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(b) != "key: value" {
+ t.Fatalf("expected 'key: value', got: %#v", string(b))
+ }
+ })
+
+ t.Run("EncoderNotFound", func(t *testing.T) {
+ registry := NewEncoderRegistry()
+
+ _, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
+ if err != ErrEncoderNotFound {
+ t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
+ }
+ })
+}
diff --git a/internal/encoding/error.go b/internal/encoding/error.go
new file mode 100644
index 0000000..e4cde02
--- /dev/null
+++ b/internal/encoding/error.go
@@ -0,0 +1,7 @@
+package encoding
+
+type encodingError string
+
+func (e encodingError) Error() string {
+ return string(e)
+}
diff --git a/internal/encoding/hcl/codec.go b/internal/encoding/hcl/codec.go
new file mode 100644
index 0000000..7fde8e4
--- /dev/null
+++ b/internal/encoding/hcl/codec.go
@@ -0,0 +1,40 @@
+package hcl
+
+import (
+ "bytes"
+ "encoding/json"
+
+ "github.com/hashicorp/hcl"
+ "github.com/hashicorp/hcl/hcl/printer"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding.
+// TODO: add printer config to the codec?
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO: use printer.Format? Is the trailing newline an issue?
+
+ ast, err := hcl.Parse(string(b))
+ if err != nil {
+ return nil, err
+ }
+
+ var buf bytes.Buffer
+
+ err = printer.Fprint(&buf, ast.Node)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+ return hcl.Unmarshal(b, &v)
+}
diff --git a/internal/encoding/hcl/codec_test.go b/internal/encoding/hcl/codec_test.go
new file mode 100644
index 0000000..7e48057
--- /dev/null
+++ b/internal/encoding/hcl/codec_test.go
@@ -0,0 +1,140 @@
+package hcl
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+"key" = "value"
+
+// list
+"list" = ["item1", "item2", "item3"]
+
+/* map */
+"map" = {
+ "key" = "value"
+}
+
+/*
+nested map
+*/
+"nested_map" "map" {
+ "key" = "value"
+
+ "list" = ["item1", "item2", "item3"]
+}`
+
+// encoded form of the data
+const encoded = `"key" = "value"
+
+"list" = ["item1", "item2", "item3"]
+
+"map" = {
+ "key" = "value"
+}
+
+"nested_map" "map" {
+ "key" = "value"
+
+ "list" = ["item1", "item2", "item3"]
+}`
+
+// decoded form of the data
+//
+// in case of HCL it's slightly different from Viper's internal representation
+// (eg. map is decoded into a list of maps)
+var decoded = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": []map[string]interface{}{
+ {
+ "key": "value",
+ },
+ },
+ "nested_map": []map[string]interface{}{
+ {
+ "map": []map[string]interface{}{
+ {
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+ },
+ },
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ "nested_map": map[string]interface{}{
+ "map": map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(decoded, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/encoding/ini/codec.go b/internal/encoding/ini/codec.go
new file mode 100644
index 0000000..9acd87f
--- /dev/null
+++ b/internal/encoding/ini/codec.go
@@ -0,0 +1,99 @@
+package ini
+
+import (
+ "bytes"
+ "sort"
+ "strings"
+
+ "github.com/spf13/cast"
+ "gopkg.in/ini.v1"
+)
+
+// LoadOptions contains all customized options used for load data source(s).
+// This type is added here for convenience: this way consumers can import a single package called "ini".
+type LoadOptions = ini.LoadOptions
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding.
+type Codec struct {
+ KeyDelimiter string
+ LoadOptions LoadOptions
+}
+
+func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ cfg := ini.Empty()
+ ini.PrettyFormat = false
+
+ flattened := map[string]interface{}{}
+
+ flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
+
+ keys := make([]string, 0, len(flattened))
+
+ for key := range flattened {
+ keys = append(keys, key)
+ }
+
+ sort.Strings(keys)
+
+ for _, key := range keys {
+ sectionName, keyName := "", key
+
+ lastSep := strings.LastIndex(key, ".")
+ if lastSep != -1 {
+ sectionName = key[:(lastSep)]
+ keyName = key[(lastSep + 1):]
+ }
+
+ // TODO: is this a good idea?
+ if sectionName == "default" {
+ sectionName = ""
+ }
+
+ cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key]))
+ }
+
+ var buf bytes.Buffer
+
+ _, err := cfg.WriteTo(&buf)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+func (c Codec) Decode(b []byte, v map[string]interface{}) error {
+ cfg := ini.Empty(c.LoadOptions)
+
+ err := cfg.Append(b)
+ if err != nil {
+ return err
+ }
+
+ sections := cfg.Sections()
+
+ for i := 0; i < len(sections); i++ {
+ section := sections[i]
+ keys := section.Keys()
+
+ for j := 0; j < len(keys); j++ {
+ key := keys[j]
+ value := cfg.Section(section.Name()).Key(key.Name()).String()
+
+ deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter()))
+
+ // set innermost value
+ deepestMap[key.Name()] = value
+ }
+ }
+
+ return nil
+}
+
+func (c Codec) keyDelimiter() string {
+ if c.KeyDelimiter == "" {
+ return "."
+ }
+
+ return c.KeyDelimiter
+}
diff --git a/internal/encoding/ini/codec_test.go b/internal/encoding/ini/codec_test.go
new file mode 100644
index 0000000..e54812c
--- /dev/null
+++ b/internal/encoding/ini/codec_test.go
@@ -0,0 +1,111 @@
+package ini
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `; key-value pair
+key=value ; key-value pair
+
+# map
+[map] # map
+key=%(key)s
+
+`
+
+// encoded form of the data
+const encoded = `key=value
+
+[map]
+key=value
+`
+
+// decoded form of the data
+//
+// in case of INI it's slightly different from Viper's internal representation
+// (eg. top level keys land in a section called default)
+var decoded = map[string]interface{}{
+ "DEFAULT": map[string]interface{}{
+ "key": "value",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+ })
+
+ t.Run("Default", func(t *testing.T) {
+ codec := Codec{}
+
+ data := map[string]interface{}{
+ "default": map[string]interface{}{
+ "key": "value",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ }
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+ })
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(decoded, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/encoding/ini/map_utils.go b/internal/encoding/ini/map_utils.go
new file mode 100644
index 0000000..8329856
--- /dev/null
+++ b/internal/encoding/ini/map_utils.go
@@ -0,0 +1,74 @@
+package ini
+
+import (
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
+// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
+// deepSearch scans deep maps, following the key indexes listed in the
+// sequence "path".
+// The last value is expected to be another map, and is returned.
+//
+// In case intermediate keys do not exist, or map to a non-map value,
+// a new map is created and inserted, and the search continues from there:
+// the initial map "m" may be modified!
+func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
+ for _, k := range path {
+ m2, ok := m[k]
+ if !ok {
+ // intermediate key does not exist
+ // => create it and continue from there
+ m3 := make(map[string]interface{})
+ m[k] = m3
+ m = m3
+ continue
+ }
+ m3, ok := m2.(map[string]interface{})
+ if !ok {
+ // intermediate key is a value
+ // => replace with a new map
+ m3 = make(map[string]interface{})
+ m[k] = m3
+ }
+ // continue search from here
+ m = m3
+ }
+ return m
+}
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+ if shadow != nil && prefix != "" && shadow[prefix] != nil {
+ // prefix is shadowed => nothing more to flatten
+ return shadow
+ }
+ if shadow == nil {
+ shadow = make(map[string]interface{})
+ }
+
+ var m2 map[string]interface{}
+ if prefix != "" {
+ prefix += delimiter
+ }
+ for k, val := range m {
+ fullKey := prefix + k
+ switch val.(type) {
+ case map[string]interface{}:
+ m2 = val.(map[string]interface{})
+ case map[interface{}]interface{}:
+ m2 = cast.ToStringMap(val)
+ default:
+ // immediate value
+ shadow[strings.ToLower(fullKey)] = val
+ continue
+ }
+ // recursively merge to shadow map
+ shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+ }
+ return shadow
+}
diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go
new file mode 100644
index 0000000..b8a2251
--- /dev/null
+++ b/internal/encoding/javaproperties/codec.go
@@ -0,0 +1,86 @@
+package javaproperties
+
+import (
+ "bytes"
+ "sort"
+ "strings"
+
+ "github.com/magiconair/properties"
+ "github.com/spf13/cast"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
+type Codec struct {
+ KeyDelimiter string
+
+ // Store read properties on the object so that we can write back in order with comments.
+ // This will only be used if the configuration read is a properties file.
+ // TODO: drop this feature in v2
+ // TODO: make use of the global properties object optional
+ Properties *properties.Properties
+}
+
+func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ if c.Properties == nil {
+ c.Properties = properties.NewProperties()
+ }
+
+ flattened := map[string]interface{}{}
+
+ flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
+
+ keys := make([]string, 0, len(flattened))
+
+ for key := range flattened {
+ keys = append(keys, key)
+ }
+
+ sort.Strings(keys)
+
+ for _, key := range keys {
+ _, _, err := c.Properties.Set(key, cast.ToString(flattened[key]))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var buf bytes.Buffer
+
+ _, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
+ if err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
+ var err error
+ c.Properties, err = properties.Load(b, properties.UTF8)
+ if err != nil {
+ return err
+ }
+
+ for _, key := range c.Properties.Keys() {
+ // ignore existence check: we know it's there
+ value, _ := c.Properties.Get(key)
+
+ // recursively build nested maps
+ path := strings.Split(key, c.keyDelimiter())
+ lastKey := strings.ToLower(path[len(path)-1])
+ deepestMap := deepSearch(v, path[0:len(path)-1])
+
+ // set innermost value
+ deepestMap[lastKey] = value
+ }
+
+ return nil
+}
+
+func (c Codec) keyDelimiter() string {
+ if c.KeyDelimiter == "" {
+ return "."
+ }
+
+ return c.KeyDelimiter
+}
diff --git a/internal/encoding/javaproperties/codec_test.go b/internal/encoding/javaproperties/codec_test.go
new file mode 100644
index 0000000..0a33ebf
--- /dev/null
+++ b/internal/encoding/javaproperties/codec_test.go
@@ -0,0 +1,89 @@
+package javaproperties
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `#key-value pair
+key = value
+map.key = value
+`
+
+// encoded form of the data
+const encoded = `key = value
+map.key = value
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(data, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ t.Skip("TODO: needs invalid data example")
+
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ codec.Decode([]byte(``), v)
+
+ if len(v) > 0 {
+ t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v)
+ }
+ })
+}
+
+func TestCodec_DecodeEncode(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if original != string(b) {
+ t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original)
+ }
+}
diff --git a/internal/encoding/javaproperties/map_utils.go b/internal/encoding/javaproperties/map_utils.go
new file mode 100644
index 0000000..93755ca
--- /dev/null
+++ b/internal/encoding/javaproperties/map_utils.go
@@ -0,0 +1,74 @@
+package javaproperties
+
+import (
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
+// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
+// deepSearch scans deep maps, following the key indexes listed in the
+// sequence "path".
+// The last value is expected to be another map, and is returned.
+//
+// In case intermediate keys do not exist, or map to a non-map value,
+// a new map is created and inserted, and the search continues from there:
+// the initial map "m" may be modified!
+func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
+ for _, k := range path {
+ m2, ok := m[k]
+ if !ok {
+ // intermediate key does not exist
+ // => create it and continue from there
+ m3 := make(map[string]interface{})
+ m[k] = m3
+ m = m3
+ continue
+ }
+ m3, ok := m2.(map[string]interface{})
+ if !ok {
+ // intermediate key is a value
+ // => replace with a new map
+ m3 = make(map[string]interface{})
+ m[k] = m3
+ }
+ // continue search from here
+ m = m3
+ }
+ return m
+}
+
+// flattenAndMergeMap recursively flattens the given map into a new map
+// Code is based on the function with the same name in tha main package.
+// TODO: move it to a common place
+func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
+ if shadow != nil && prefix != "" && shadow[prefix] != nil {
+ // prefix is shadowed => nothing more to flatten
+ return shadow
+ }
+ if shadow == nil {
+ shadow = make(map[string]interface{})
+ }
+
+ var m2 map[string]interface{}
+ if prefix != "" {
+ prefix += delimiter
+ }
+ for k, val := range m {
+ fullKey := prefix + k
+ switch val.(type) {
+ case map[string]interface{}:
+ m2 = val.(map[string]interface{})
+ case map[interface{}]interface{}:
+ m2 = cast.ToStringMap(val)
+ default:
+ // immediate value
+ shadow[strings.ToLower(fullKey)] = val
+ continue
+ }
+ // recursively merge to shadow map
+ shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
+ }
+ return shadow
+}
diff --git a/internal/encoding/json/codec.go b/internal/encoding/json/codec.go
new file mode 100644
index 0000000..1b7caac
--- /dev/null
+++ b/internal/encoding/json/codec.go
@@ -0,0 +1,17 @@
+package json
+
+import (
+ "encoding/json"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ // TODO: expose prefix and indent in the Codec as setting?
+ return json.MarshalIndent(v, "", " ")
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+ return json.Unmarshal(b, &v)
+}
diff --git a/internal/encoding/json/codec_test.go b/internal/encoding/json/codec_test.go
new file mode 100644
index 0000000..f4a71df
--- /dev/null
+++ b/internal/encoding/json/codec_test.go
@@ -0,0 +1,95 @@
+package json
+
+import (
+ "reflect"
+ "testing"
+)
+
+// encoded form of the data
+const encoded = `{
+ "key": "value",
+ "list": [
+ "item1",
+ "item2",
+ "item3"
+ ],
+ "map": {
+ "key": "value"
+ },
+ "nested_map": {
+ "map": {
+ "key": "value",
+ "list": [
+ "item1",
+ "item2",
+ "item3"
+ ]
+ }
+ }
+}`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ "nested_map": map[string]interface{}{
+ "map": map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(encoded), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(data, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/encoding/toml/codec.go b/internal/encoding/toml/codec.go
new file mode 100644
index 0000000..a993c59
--- /dev/null
+++ b/internal/encoding/toml/codec.go
@@ -0,0 +1,16 @@
+package toml
+
+import (
+ "github.com/pelletier/go-toml/v2"
+)
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ return toml.Marshal(v)
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+ return toml.Unmarshal(b, &v)
+}
diff --git a/internal/encoding/toml/codec_test.go b/internal/encoding/toml/codec_test.go
new file mode 100644
index 0000000..acbe905
--- /dev/null
+++ b/internal/encoding/toml/codec_test.go
@@ -0,0 +1,105 @@
+package toml
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+key = "value"
+list = ["item1", "item2", "item3"]
+
+[map]
+key = "value"
+
+# nested
+# map
+[nested_map]
+[nested_map.map]
+key = "value"
+list = [
+ "item1",
+ "item2",
+ "item3",
+]
+`
+
+// encoded form of the data
+const encoded = `key = 'value'
+list = ['item1', 'item2', 'item3']
+
+[map]
+key = 'value'
+
+[nested_map]
+[nested_map.map]
+key = 'value'
+list = ['item1', 'item2', 'item3']
+`
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ "nested_map": map[string]interface{}{
+ "map": map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(data, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/encoding/yaml/codec.go b/internal/encoding/yaml/codec.go
new file mode 100644
index 0000000..82dc136
--- /dev/null
+++ b/internal/encoding/yaml/codec.go
@@ -0,0 +1,14 @@
+package yaml
+
+import "gopkg.in/yaml.v3"
+
+// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
+type Codec struct{}
+
+func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
+ return yaml.Marshal(v)
+}
+
+func (Codec) Decode(b []byte, v map[string]interface{}) error {
+ return yaml.Unmarshal(b, &v)
+}
diff --git a/internal/encoding/yaml/codec_test.go b/internal/encoding/yaml/codec_test.go
new file mode 100644
index 0000000..9fe3838
--- /dev/null
+++ b/internal/encoding/yaml/codec_test.go
@@ -0,0 +1,136 @@
+package yaml
+
+import (
+ "reflect"
+ "testing"
+)
+
+// original form of the data
+const original = `# key-value pair
+key: value
+list:
+ - item1
+ - item2
+ - item3
+map:
+ key: value
+
+# nested
+# map
+nested_map:
+ map:
+ key: value
+ list:
+ - item1
+ - item2
+ - item3
+`
+
+// encoded form of the data
+const encoded = `key: value
+list:
+ - item1
+ - item2
+ - item3
+map:
+ key: value
+nested_map:
+ map:
+ key: value
+ list:
+ - item1
+ - item2
+ - item3
+`
+
+// decoded form of the data
+//
+// in case of YAML it's slightly different from Viper's internal representation
+// (eg. map is decoded into a map with interface key)
+var decoded = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ "nested_map": map[string]interface{}{
+ "map": map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+}
+
+// Viper's internal representation
+var data = map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ "map": map[string]interface{}{
+ "key": "value",
+ },
+ "nested_map": map[string]interface{}{
+ "map": map[string]interface{}{
+ "key": "value",
+ "list": []interface{}{
+ "item1",
+ "item2",
+ "item3",
+ },
+ },
+ },
+}
+
+func TestCodec_Encode(t *testing.T) {
+ codec := Codec{}
+
+ b, err := codec.Encode(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if encoded != string(b) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
+ }
+}
+
+func TestCodec_Decode(t *testing.T) {
+ t.Run("OK", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(original), v)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(decoded, v) {
+ t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
+ }
+ })
+
+ t.Run("InvalidData", func(t *testing.T) {
+ codec := Codec{}
+
+ v := map[string]interface{}{}
+
+ err := codec.Decode([]byte(`invalid data`), v)
+ if err == nil {
+ t.Fatal("expected decoding to fail")
+ }
+
+ t.Logf("decoding failed as expected: %s", err)
+ })
+}
diff --git a/internal/testutil/env_go1_16.go b/internal/testutil/env_go1_16.go
new file mode 100644
index 0000000..8769a44
--- /dev/null
+++ b/internal/testutil/env_go1_16.go
@@ -0,0 +1,40 @@
+//go:build !go1.17
+// +build !go1.17
+
+package testutil
+
+import (
+ "os"
+ "testing"
+)
+
+// Based on https://github.com/frankban/quicktest/blob/577841610793d24f99e31cc2c0ef3a541fefd7c7/patch.go#L34-L64
+// Licensed under the MIT license
+// Copyright (c) 2017 Canonical Ltd.
+
+// Setenv sets an environment variable to a temporary value for the
+// duration of the test.
+//
+// At the end of the test (see "Deferred execution" in the package docs), the
+// environment variable is returned to its original value.
+func Setenv(t *testing.T, name, val string) {
+ setenv(t, name, val, true)
+}
+
+// setenv sets or unsets an environment variable to a temporary value for the
+// duration of the test
+func setenv(t *testing.T, name, val string, valOK bool) {
+ oldVal, oldOK := os.LookupEnv(name)
+ if valOK {
+ os.Setenv(name, val)
+ } else {
+ os.Unsetenv(name)
+ }
+ t.Cleanup(func() {
+ if oldOK {
+ os.Setenv(name, oldVal)
+ } else {
+ os.Unsetenv(name)
+ }
+ })
+}
diff --git a/internal/testutil/env_go1_17.go b/internal/testutil/env_go1_17.go
new file mode 100644
index 0000000..c7bf1fe
--- /dev/null
+++ b/internal/testutil/env_go1_17.go
@@ -0,0 +1,18 @@
+//go:build go1.17
+// +build go1.17
+
+package testutil
+
+import (
+ "testing"
+)
+
+// Setenv sets an environment variable to a temporary value for the
+// duration of the test.
+//
+// This shim can be removed once support for Go <1.17 is dropped.
+func Setenv(t *testing.T, name, val string) {
+ t.Helper()
+
+ t.Setenv(name, val)
+}
diff --git a/internal/testutil/filepath.go b/internal/testutil/filepath.go
new file mode 100644
index 0000000..e49be7e
--- /dev/null
+++ b/internal/testutil/filepath.go
@@ -0,0 +1,18 @@
+package testutil
+
+import (
+ "path/filepath"
+ "testing"
+)
+
+// AbsFilePath calls filepath.Abs on path.
+func AbsFilePath(t *testing.T, path string) string {
+ t.Helper()
+
+ s, err := filepath.Abs(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return s
+}
diff --git a/logger.go b/logger.go
new file mode 100644
index 0000000..a64e144
--- /dev/null
+++ b/logger.go
@@ -0,0 +1,77 @@
+package viper
+
+import (
+ "fmt"
+
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+// Logger is a unified interface for various logging use cases and practices, including:
+// - leveled logging
+// - structured logging
+type Logger interface {
+ // Trace logs a Trace event.
+ //
+ // Even more fine-grained information than Debug events.
+ // Loggers not supporting this level should fall back to Debug.
+ Trace(msg string, keyvals ...interface{})
+
+ // Debug logs a Debug event.
+ //
+ // A verbose series of information events.
+ // They are useful when debugging the system.
+ Debug(msg string, keyvals ...interface{})
+
+ // Info logs an Info event.
+ //
+ // General information about what's happening inside the system.
+ Info(msg string, keyvals ...interface{})
+
+ // Warn logs a Warn(ing) event.
+ //
+ // Non-critical events that should be looked at.
+ Warn(msg string, keyvals ...interface{})
+
+ // Error logs an Error event.
+ //
+ // Critical events that require immediate attention.
+ // Loggers commonly provide Fatal and Panic levels above Error level,
+ // but exiting and panicing is out of scope for a logging library.
+ Error(msg string, keyvals ...interface{})
+}
+
+type jwwLogger struct{}
+
+func (jwwLogger) Trace(msg string, keyvals ...interface{}) {
+ jww.TRACE.Printf(jwwLogMessage(msg, keyvals...))
+}
+
+func (jwwLogger) Debug(msg string, keyvals ...interface{}) {
+ jww.DEBUG.Printf(jwwLogMessage(msg, keyvals...))
+}
+
+func (jwwLogger) Info(msg string, keyvals ...interface{}) {
+ jww.INFO.Printf(jwwLogMessage(msg, keyvals...))
+}
+
+func (jwwLogger) Warn(msg string, keyvals ...interface{}) {
+ jww.WARN.Printf(jwwLogMessage(msg, keyvals...))
+}
+
+func (jwwLogger) Error(msg string, keyvals ...interface{}) {
+ jww.ERROR.Printf(jwwLogMessage(msg, keyvals...))
+}
+
+func jwwLogMessage(msg string, keyvals ...interface{}) string {
+ out := msg
+
+ if len(keyvals) > 0 && len(keyvals)%2 == 1 {
+ keyvals = append(keyvals, nil)
+ }
+
+ for i := 0; i <= len(keyvals)-2; i += 2 {
+ out = fmt.Sprintf("%s %v=%v", out, keyvals[i], keyvals[i+1])
+ }
+
+ return out
+}
diff --git a/overrides_test.go b/overrides_test.go
index dd2aa9b..8048204 100644
--- a/overrides_test.go
+++ b/overrides_test.go
@@ -78,6 +78,7 @@ func TestNestedOverrides(t *testing.T) {
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
+
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
diff --git a/remote/remote.go b/remote/remote.go
index 3f9d049..63e69c1 100644
--- a/remote/remote.go
+++ b/remote/remote.go
@@ -10,10 +10,11 @@ import (
"bytes"
"io"
"os"
+ "strings"
- "github.com/ShaleApps/viper"
+ crypt "github.com/sagikazarmark/crypt/config"
- crypt "github.com/xordataexchange/crypt/config"
+ "github.com/spf13/viper"
)
type remoteConfigProvider struct{}
@@ -75,6 +76,7 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
var cm crypt.ConfigManager
var err error
+ endpoints := strings.Split(rp.Endpoint(), ";")
if rp.SecretKeyring() != "" {
var kr *os.File
kr, err = os.Open(rp.SecretKeyring())
@@ -82,16 +84,26 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
return nil, err
}
defer kr.Close()
- if rp.Provider() == "etcd" {
- cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
- } else {
- cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
+ switch rp.Provider() {
+ case "etcd":
+ cm, err = crypt.NewEtcdConfigManager(endpoints, kr)
+ case "etcd3":
+ cm, err = crypt.NewEtcdV3ConfigManager(endpoints, kr)
+ case "firestore":
+ cm, err = crypt.NewFirestoreConfigManager(endpoints, kr)
+ default:
+ cm, err = crypt.NewConsulConfigManager(endpoints, kr)
}
} else {
- if rp.Provider() == "etcd" {
- cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
- } else {
- cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
+ switch rp.Provider() {
+ case "etcd":
+ cm, err = crypt.NewStandardEtcdConfigManager(endpoints)
+ case "etcd3":
+ cm, err = crypt.NewStandardEtcdV3ConfigManager(endpoints)
+ case "firestore":
+ cm, err = crypt.NewStandardFirestoreConfigManager(endpoints)
+ default:
+ cm, err = crypt.NewStandardConsulConfigManager(endpoints)
}
}
if err != nil {
diff --git a/util.go b/util.go
index b788969..64e6575 100644
--- a/util.go
+++ b/util.go
@@ -18,9 +18,7 @@ import (
"strings"
"unicode"
- "github.com/spf13/afero"
"github.com/spf13/cast"
- jww "github.com/spf13/jwalterweatherman"
)
// ConfigParseError denotes failing to parse configuration file.
@@ -66,18 +64,25 @@ func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
return nm
}
+func insensitiviseVal(val interface{}) interface{} {
+ switch val.(type) {
+ case map[interface{}]interface{}:
+ // nested map: cast and recursively insensitivise
+ val = cast.ToStringMap(val)
+ insensitiviseMap(val.(map[string]interface{}))
+ case map[string]interface{}:
+ // nested map: recursively insensitivise
+ insensitiviseMap(val.(map[string]interface{}))
+ case []interface{}:
+ // nested array: recursively insensitivise
+ insensitiveArray(val.([]interface{}))
+ }
+ return val
+}
+
func insensitiviseMap(m map[string]interface{}) {
for key, val := range m {
- switch val.(type) {
- case map[interface{}]interface{}:
- // nested map: cast and recursively insensitivise
- val = cast.ToStringMap(val)
- insensitiviseMap(val.(map[string]interface{}))
- case map[string]interface{}:
- // nested map: recursively insensitivise
- insensitiviseMap(val.(map[string]interface{}))
- }
-
+ val = insensitiviseVal(val)
lower := strings.ToLower(key)
if key != lower {
// remove old key (not lower-cased)
@@ -88,17 +93,20 @@ func insensitiviseMap(m map[string]interface{}) {
}
}
-func absPathify(inPath string) string {
- jww.INFO.Println("Trying to resolve absolute path to", inPath)
+func insensitiveArray(a []interface{}) {
+ for i, val := range a {
+ a[i] = insensitiviseVal(val)
+ }
+}
- if strings.HasPrefix(inPath, "$HOME") {
+func absPathify(logger Logger, inPath string) string {
+ logger.Info("trying to resolve absolute path", "path", inPath)
+
+ if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
inPath = userHomeDir() + inPath[5:]
}
- if strings.HasPrefix(inPath, "$") {
- end := strings.Index(inPath, string(os.PathSeparator))
- inPath = os.Getenv(inPath[1:end]) + inPath[end:]
- }
+ inPath = os.ExpandEnv(inPath)
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
@@ -109,21 +117,9 @@ func absPathify(inPath string) string {
return filepath.Clean(p)
}
- jww.ERROR.Println("Couldn't discover absolute path")
- jww.ERROR.Println(err)
- return ""
-}
+ logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
-// Check if file Exists
-func exists(fs afero.Fs, path string) (bool, error) {
- stat, err := fs.Stat(path)
- if err == nil {
- return !stat.IsDir(), nil
- }
- if os.IsNotExist(err) {
- return false, nil
- }
- return false, err
+ return ""
}
func stringInSlice(a string, list []string) bool {
diff --git a/util_test.go b/util_test.go
index 0af80bb..cb4e620 100644
--- a/util_test.go
+++ b/util_test.go
@@ -11,25 +11,29 @@
package viper
import (
+ "os"
+ "path/filepath"
"reflect"
"testing"
+
+ "github.com/spf13/viper/internal/testutil"
)
func TestCopyAndInsensitiviseMap(t *testing.T) {
var (
given = map[string]interface{}{
"Foo": 32,
- "Bar": map[interface{}]interface {
- }{
+ "Bar": map[interface{}]interface{}{
"ABc": "A",
- "cDE": "B"},
+ "cDE": "B",
+ },
}
expected = map[string]interface{}{
"foo": 32,
- "bar": map[string]interface {
- }{
+ "bar": map[string]interface{}{
"abc": "A",
- "cde": "B"},
+ "cde": "B",
+ },
}
)
@@ -52,3 +56,40 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
t.Fatal("Input map changed")
}
}
+
+func TestAbsPathify(t *testing.T) {
+ skipWindows(t)
+
+ home := userHomeDir()
+ homer := filepath.Join(home, "homer")
+ wd, _ := os.Getwd()
+
+ testutil.Setenv(t, "HOMER_ABSOLUTE_PATH", homer)
+ testutil.Setenv(t, "VAR_WITH_RELATIVE_PATH", "relative")
+
+ tests := []struct {
+ input string
+ output string
+ }{
+ {"", wd},
+ {"sub", filepath.Join(wd, "sub")},
+ {"./", wd},
+ {"./sub", filepath.Join(wd, "sub")},
+ {"$HOME", home},
+ {"$HOME/", home},
+ {"$HOME/sub", filepath.Join(home, "sub")},
+ {"$HOMER_ABSOLUTE_PATH", homer},
+ {"$HOMER_ABSOLUTE_PATH/", homer},
+ {"$HOMER_ABSOLUTE_PATH/sub", filepath.Join(homer, "sub")},
+ {"$VAR_WITH_RELATIVE_PATH", filepath.Join(wd, "relative")},
+ {"$VAR_WITH_RELATIVE_PATH/", filepath.Join(wd, "relative")},
+ {"$VAR_WITH_RELATIVE_PATH/sub", filepath.Join(wd, "relative", "sub")},
+ }
+
+ for _, test := range tests {
+ got := absPathify(jwwLogger{}, test.input)
+ if got != test.output {
+ t.Errorf("Got %v\nexpected\n%q", got, test.output)
+ }
+ }
+}
diff --git a/viper.go b/viper.go
index 5a3c905..fa6f3e3 100644
--- a/viper.go
+++ b/viper.go
@@ -22,7 +22,6 @@ package viper
import (
"bytes"
"encoding/csv"
- "encoding/json"
"errors"
"fmt"
"io"
@@ -30,23 +29,25 @@ import (
"os"
"path/filepath"
"reflect"
+ "strconv"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
- "github.com/hashicorp/hcl"
- "github.com/hashicorp/hcl/hcl/printer"
- "github.com/magiconair/properties"
"github.com/mitchellh/mapstructure"
- "github.com/pelletier/go-toml"
"github.com/spf13/afero"
"github.com/spf13/cast"
- jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
- "github.com/subosito/gotenv"
- "gopkg.in/ini.v1"
- "gopkg.in/yaml.v2"
+
+ "github.com/spf13/viper/internal/encoding"
+ "github.com/spf13/viper/internal/encoding/dotenv"
+ "github.com/spf13/viper/internal/encoding/hcl"
+ "github.com/spf13/viper/internal/encoding/ini"
+ "github.com/spf13/viper/internal/encoding/javaproperties"
+ "github.com/spf13/viper/internal/encoding/json"
+ "github.com/spf13/viper/internal/encoding/toml"
+ "github.com/spf13/viper/internal/encoding/yaml"
)
// ConfigMarshalError happens when failing to marshal the configuration.
@@ -131,10 +132,10 @@ type DecoderConfigOption func(*mapstructure.DecoderConfig)
// DecodeHook returns a DecoderConfigOption which overrides the default
// DecoderConfig.DecodeHook value, the default is:
//
-// mapstructure.ComposeDecodeHookFunc(
-// mapstructure.StringToTimeDurationHookFunc(),
-// mapstructure.StringToSliceHookFunc(","),
-// )
+// mapstructure.ComposeDecodeHookFunc(
+// mapstructure.StringToTimeDurationHookFunc(),
+// mapstructure.StringToSliceHookFunc(","),
+// )
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
return func(c *mapstructure.DecoderConfig) {
c.DecodeHook = hook
@@ -155,18 +156,18 @@ func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
//
// For example, if values from the following sources were loaded:
//
-// Defaults : {
-// "secret": "",
-// "user": "default",
-// "endpoint": "https://localhost"
-// }
-// Config : {
-// "user": "root"
-// "secret": "defaultsecret"
-// }
-// Env : {
-// "secret": "somesecretkey"
-// }
+// Defaults : {
+// "secret": "",
+// "user": "default",
+// "endpoint": "https://localhost"
+// }
+// Config : {
+// "user": "root"
+// "secret": "defaultsecret"
+// }
+// Env : {
+// "secret": "somesecretkey"
+// }
//
// The resulting config will have the following values:
//
@@ -175,6 +176,8 @@ func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
// "user": "root",
// "endpoint": "https://localhost"
// }
+//
+// Note: Vipers are not safe for concurrent Get() and Set() operations.
type Viper struct {
// Delimiter that separates a list of keys
// used to access a nested value in one go
@@ -196,25 +199,30 @@ type Viper struct {
configPermissions os.FileMode
envPrefix string
+ // Specific commands for ini parsing
+ iniLoadOptions ini.LoadOptions
+
automaticEnvApplied bool
envKeyReplacer StringReplacer
allowEmptyEnv bool
+ parents []string
config map[string]interface{}
override map[string]interface{}
defaults map[string]interface{}
kvstore map[string]interface{}
pflags map[string]FlagValue
- env map[string]string
+ env map[string][]string
aliases map[string]string
typeByDefValue bool
- // Store read properties on the object so that we can write back in order with comments.
- // This will only be used if the configuration read is a properties file.
- properties *properties.Properties
-
onConfigChange func(fsnotify.Event)
- mu *sync.RWMutex
+
+ logger Logger
+
+ // TODO: should probably be protected with a mutex
+ encoderRegistry *encoding.EncoderRegistry
+ decoderRegistry *encoding.DecoderRegistry
}
// New returns an initialized Viper instance.
@@ -222,17 +230,21 @@ func New() *Viper {
v := new(Viper)
v.keyDelim = "."
v.configName = "config"
- v.configPermissions = os.FileMode(0644)
+ v.configPermissions = os.FileMode(0o644)
v.fs = afero.NewOsFs()
v.config = make(map[string]interface{})
+ v.parents = []string{}
v.override = make(map[string]interface{})
v.defaults = make(map[string]interface{})
v.kvstore = make(map[string]interface{})
v.pflags = make(map[string]FlagValue)
- v.env = make(map[string]string)
+ v.env = make(map[string][]string)
v.aliases = make(map[string]string)
v.typeByDefValue = false
- v.mu = &sync.RWMutex{}
+ v.logger = jwwLogger{}
+
+ v.resetEncoding()
+
return v
}
@@ -279,6 +291,8 @@ func NewWithOptions(opts ...Option) *Viper {
opt.apply(v)
}
+ v.resetEncoding()
+
return v
}
@@ -287,8 +301,86 @@ func NewWithOptions(opts ...Option) *Viper {
// can use it in their testing as well.
func Reset() {
v = New()
- SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
- SupportedRemoteProviders = []string{"etcd", "consul"}
+ SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
+ SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
+}
+
+// TODO: make this lazy initialization instead
+func (v *Viper) resetEncoding() {
+ encoderRegistry := encoding.NewEncoderRegistry()
+ decoderRegistry := encoding.NewDecoderRegistry()
+
+ {
+ codec := yaml.Codec{}
+
+ encoderRegistry.RegisterEncoder("yaml", codec)
+ decoderRegistry.RegisterDecoder("yaml", codec)
+
+ encoderRegistry.RegisterEncoder("yml", codec)
+ decoderRegistry.RegisterDecoder("yml", codec)
+ }
+
+ {
+ codec := json.Codec{}
+
+ encoderRegistry.RegisterEncoder("json", codec)
+ decoderRegistry.RegisterDecoder("json", codec)
+ }
+
+ {
+ codec := toml.Codec{}
+
+ encoderRegistry.RegisterEncoder("toml", codec)
+ decoderRegistry.RegisterDecoder("toml", codec)
+ }
+
+ {
+ codec := hcl.Codec{}
+
+ encoderRegistry.RegisterEncoder("hcl", codec)
+ decoderRegistry.RegisterDecoder("hcl", codec)
+
+ encoderRegistry.RegisterEncoder("tfvars", codec)
+ decoderRegistry.RegisterDecoder("tfvars", codec)
+ }
+
+ {
+ codec := ini.Codec{
+ KeyDelimiter: v.keyDelim,
+ LoadOptions: v.iniLoadOptions,
+ }
+
+ encoderRegistry.RegisterEncoder("ini", codec)
+ decoderRegistry.RegisterDecoder("ini", codec)
+ }
+
+ {
+ codec := &javaproperties.Codec{
+ KeyDelimiter: v.keyDelim,
+ }
+
+ encoderRegistry.RegisterEncoder("properties", codec)
+ decoderRegistry.RegisterDecoder("properties", codec)
+
+ encoderRegistry.RegisterEncoder("props", codec)
+ decoderRegistry.RegisterDecoder("props", codec)
+
+ encoderRegistry.RegisterEncoder("prop", codec)
+ decoderRegistry.RegisterDecoder("prop", codec)
+ }
+
+ {
+ codec := &dotenv.Codec{}
+
+ encoderRegistry.RegisterEncoder("dotenv", codec)
+ decoderRegistry.RegisterDecoder("dotenv", codec)
+
+ encoderRegistry.RegisterEncoder("env", codec)
+ decoderRegistry.RegisterDecoder("env", codec)
+ }
+
+ v.encoderRegistry = encoderRegistry
+ v.decoderRegistry = decoderRegistry
}
type defaultRemoteProvider struct {
@@ -326,23 +418,28 @@ type RemoteProvider interface {
}
// SupportedExts are universally supported extensions.
-var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
+var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
// SupportedRemoteProviders are universally supported remote providers.
-var SupportedRemoteProviders = []string{"etcd", "consul"}
+var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
+// OnConfigChange sets the event handler that is called when a config file changes.
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
+
+// OnConfigChange sets the event handler that is called when a config file changes.
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
v.onConfigChange = run
}
+// WatchConfig starts watching a config file for changes.
func WatchConfig() { v.WatchConfig() }
+// WatchConfig starts watching a config file for changes.
func (v *Viper) WatchConfig() {
initWG := sync.WaitGroup{}
initWG.Add(1)
go func() {
- watcher, err := fsnotify.NewWatcher()
+ watcher, err := newWatcher()
if err != nil {
log.Fatal(err)
}
@@ -373,9 +470,8 @@ func (v *Viper) WatchConfig() {
// we only care about the config file with the following cases:
// 1 - if the config file was modified or created
// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
- const writeOrCreateMask = fsnotify.Write | fsnotify.Create
if (filepath.Clean(event.Name) == configFile &&
- event.Op&writeOrCreateMask != 0) ||
+ (event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||
(currentConfigFile != "" && currentConfigFile != realConfigFile) {
realConfigFile = currentConfigFile
err := v.ReadInConfig()
@@ -385,8 +481,7 @@ func (v *Viper) WatchConfig() {
if v.onConfigChange != nil {
v.onConfigChange(event)
}
- } else if filepath.Clean(event.Name) == configFile &&
- event.Op&fsnotify.Remove&fsnotify.Remove != 0 {
+ } else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
eventsWG.Done()
return
}
@@ -410,6 +505,7 @@ func (v *Viper) WatchConfig() {
// SetConfigFile explicitly defines the path, name and extension of the config file.
// Viper will use this and not check any of the config paths.
func SetConfigFile(in string) { v.SetConfigFile(in) }
+
func (v *Viper) SetConfigFile(in string) {
if in != "" {
v.configFile = in
@@ -420,6 +516,7 @@ func (v *Viper) SetConfigFile(in string) {
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }
+
func (v *Viper) SetEnvPrefix(in string) {
if in != "" {
v.envPrefix = in
@@ -438,6 +535,7 @@ func (v *Viper) mergeWithEnvPrefix(in string) string {
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
+
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
v.allowEmptyEnv = allowEmptyEnv
}
@@ -466,10 +564,12 @@ func (v *Viper) ConfigFileUsed() string { return v.configFile }
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func AddConfigPath(in string) { v.AddConfigPath(in) }
+
func (v *Viper) AddConfigPath(in string) {
if in != "" {
- absin := absPathify(in)
- jww.INFO.Println("adding", absin, "to paths to search")
+ absin := absPathify(v.logger, in)
+
+ v.logger.Info("adding path to search paths", "path", absin)
if !stringInSlice(absin, v.configPaths) {
v.configPaths = append(v.configPaths, absin)
}
@@ -478,7 +578,7 @@ func (v *Viper) AddConfigPath(in string) {
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
-// provider is a string value, "etcd" or "consul" are currently supported.
+// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
@@ -487,12 +587,14 @@ func (v *Viper) AddConfigPath(in string) {
func AddRemoteProvider(provider, endpoint, path string) error {
return v.AddRemoteProvider(provider, endpoint, path)
}
+
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
if !stringInSlice(provider, SupportedRemoteProviders) {
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
- jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
+ v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
+
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
@@ -507,14 +609,14 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
-// provider is a string value, "etcd" or "consul" are currently supported.
+// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp"
-// Secure Remote Providers are implemented with github.com/xordataexchange/crypt
+// Secure Remote Providers are implemented with github.com/bketelsen/crypt
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
}
@@ -524,7 +626,8 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
- jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
+ v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
+
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
@@ -551,8 +654,6 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
// Returns nil if not found.
// Note: This assumes that the path entries and map keys are lower cased.
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
- //v.mu.Lock()
- //defer v.mu.Unlock()
if len(path) == 0 {
return source
}
@@ -580,9 +681,9 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac
return nil
}
-// searchMapWithPathPrefixes recursively searches for a value for path in source map.
+// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
//
-// While searchMap() considers each path element as a single map key, this
+// While searchMap() considers each path element as a single map key or slice index, this
// function searches for, and prioritizes, merged path elements.
// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar"
// is also defined, this latter value is returned for path ["foo", "bar"].
@@ -591,9 +692,7 @@ func (v *Viper) searchMap(source map[string]interface{}, path []string) interfac
// in their keys).
//
// Note: This assumes that the path entries and map keys are lower cased.
-func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []string) interface{} {
- //v.mu.Lock()
- //defer v.mu.Unlock()
+func (v *Viper) searchIndexableWithPathPrefixes(source interface{}, path []string) interface{} {
if len(path) == 0 {
return source
}
@@ -602,29 +701,86 @@ func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []
for i := len(path); i > 0; i-- {
prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))
- next, ok := source[prefixKey]
- if ok {
- // Fast path
- if i == len(path) {
- return next
- }
-
- // Nested case
- var val interface{}
- switch next.(type) {
- case map[interface{}]interface{}:
- val = v.searchMapWithPathPrefixes(cast.ToStringMap(next), path[i:])
- case map[string]interface{}:
- // Type assertion is safe here since it is only reached
- // if the type of `next` is the same as the type being asserted
- val = v.searchMapWithPathPrefixes(next.(map[string]interface{}), path[i:])
- default:
- // got a value but nested key expected, do nothing and look for next prefix
- }
- if val != nil {
- return val
- }
+ var val interface{}
+ switch sourceIndexable := source.(type) {
+ case []interface{}:
+ val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)
+ case map[string]interface{}:
+ val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path)
}
+ if val != nil {
+ return val
+ }
+ }
+
+ // not found
+ return nil
+}
+
+// searchSliceWithPathPrefixes searches for a value for path in sourceSlice
+//
+// This function is part of the searchIndexableWithPathPrefixes recurring search and
+// should not be called directly from functions other than searchIndexableWithPathPrefixes.
+func (v *Viper) searchSliceWithPathPrefixes(
+ sourceSlice []interface{},
+ prefixKey string,
+ pathIndex int,
+ path []string,
+) interface{} {
+ // if the prefixKey is not a number or it is out of bounds of the slice
+ index, err := strconv.Atoi(prefixKey)
+ if err != nil || len(sourceSlice) <= index {
+ return nil
+ }
+
+ next := sourceSlice[index]
+
+ // Fast path
+ if pathIndex == len(path) {
+ return next
+ }
+
+ switch n := next.(type) {
+ case map[interface{}]interface{}:
+ return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
+ case map[string]interface{}, []interface{}:
+ return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
+ default:
+ // got a value but nested key expected, do nothing and look for next prefix
+ }
+
+ // not found
+ return nil
+}
+
+// searchMapWithPathPrefixes searches for a value for path in sourceMap
+//
+// This function is part of the searchIndexableWithPathPrefixes recurring search and
+// should not be called directly from functions other than searchIndexableWithPathPrefixes.
+func (v *Viper) searchMapWithPathPrefixes(
+ sourceMap map[string]interface{},
+ prefixKey string,
+ pathIndex int,
+ path []string,
+) interface{} {
+ next, ok := sourceMap[prefixKey]
+ if !ok {
+ return nil
+ }
+
+ // Fast path
+ if pathIndex == len(path) {
+ return next
+ }
+
+ // Nested case
+ switch n := next.(type) {
+ case map[interface{}]interface{}:
+ return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
+ case map[string]interface{}, []interface{}:
+ return v.searchIndexableWithPathPrefixes(n, path[pathIndex:])
+ default:
+ // got a value but nested key expected, do nothing and look for next prefix
}
// not found
@@ -634,7 +790,8 @@ func (v *Viper) searchMapWithPathPrefixes(source map[string]interface{}, path []
// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere
// on its path in the map.
// e.g., if "foo.bar" has a value in the given map, it βshadowsβ
-// "foo.bar.baz" in a lower-priority map
+//
+// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string {
var parentVal interface{}
for i := 1; i < len(path); i++ {
@@ -659,7 +816,8 @@ func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{})
// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere
// in a sub-path of the map.
// e.g., if "foo.bar" has a value in the given map, it βshadowsβ
-// "foo.bar.baz" in a lower-priority map
+//
+// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// unify input map
var m map[string]interface{}
@@ -684,7 +842,8 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere
// in the environment, when automatic env is on.
// e.g., if "foo.bar" has a value in the environment, it βshadowsβ
-// "foo.bar.baz" in a lower-priority map
+//
+// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
for i := 1; i < len(path); i++ {
@@ -705,12 +864,13 @@ func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
// would return a string slice for the key if the key's type is inferred by
// the default value and the Get function would return:
//
-// []string {"a", "b", "c"}
+// []string {"a", "b", "c"}
//
// Otherwise the Get function would return:
//
-// "a b c"
+// "a b c"
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
+
func (v *Viper) SetTypeByDefaultValue(enable bool) {
v.typeByDefValue = enable
}
@@ -728,9 +888,8 @@ func GetViper() *Viper {
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func Get(key string) interface{} { return v.Get(key) }
+
func (v *Viper) Get(key string) interface{} {
- v.mu.Lock()
- defer v.mu.Unlock()
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, true)
if val == nil {
@@ -771,6 +930,8 @@ func (v *Viper) Get(key string) interface{} {
return cast.ToStringSlice(val)
case []int:
return cast.ToIntSlice(val)
+ case []time.Duration:
+ return cast.ToDurationSlice(val)
}
}
@@ -780,6 +941,7 @@ func (v *Viper) Get(key string) interface{} {
// Sub returns new Viper instance representing a sub tree of this instance.
// Sub is case-insensitive for a key.
func Sub(key string) *Viper { return v.Sub(key) }
+
func (v *Viper) Sub(key string) *Viper {
subv := New()
data := v.Get(key)
@@ -788,6 +950,10 @@ func (v *Viper) Sub(key string) *Viper {
}
if reflect.TypeOf(data).Kind() == reflect.Map {
+ subv.parents = append(v.parents, strings.ToLower(key))
+ subv.automaticEnvApplied = v.automaticEnvApplied
+ subv.envPrefix = v.envPrefix
+ subv.envKeyReplacer = v.envKeyReplacer
subv.config = cast.ToStringMap(data)
return subv
}
@@ -796,96 +962,119 @@ func (v *Viper) Sub(key string) *Viper {
// GetString returns the value associated with the key as a string.
func GetString(key string) string { return v.GetString(key) }
+
func (v *Viper) GetString(key string) string {
return cast.ToString(v.Get(key))
}
// GetBool returns the value associated with the key as a boolean.
func GetBool(key string) bool { return v.GetBool(key) }
+
func (v *Viper) GetBool(key string) bool {
return cast.ToBool(v.Get(key))
}
// GetInt returns the value associated with the key as an integer.
func GetInt(key string) int { return v.GetInt(key) }
+
func (v *Viper) GetInt(key string) int {
return cast.ToInt(v.Get(key))
}
// GetInt32 returns the value associated with the key as an integer.
func GetInt32(key string) int32 { return v.GetInt32(key) }
+
func (v *Viper) GetInt32(key string) int32 {
return cast.ToInt32(v.Get(key))
}
// GetInt64 returns the value associated with the key as an integer.
func GetInt64(key string) int64 { return v.GetInt64(key) }
+
func (v *Viper) GetInt64(key string) int64 {
return cast.ToInt64(v.Get(key))
}
// GetUint returns the value associated with the key as an unsigned integer.
func GetUint(key string) uint { return v.GetUint(key) }
+
func (v *Viper) GetUint(key string) uint {
return cast.ToUint(v.Get(key))
}
+// GetUint16 returns the value associated with the key as an unsigned integer.
+func GetUint16(key string) uint16 { return v.GetUint16(key) }
+
+func (v *Viper) GetUint16(key string) uint16 {
+ return cast.ToUint16(v.Get(key))
+}
+
// GetUint32 returns the value associated with the key as an unsigned integer.
func GetUint32(key string) uint32 { return v.GetUint32(key) }
+
func (v *Viper) GetUint32(key string) uint32 {
return cast.ToUint32(v.Get(key))
}
// GetUint64 returns the value associated with the key as an unsigned integer.
func GetUint64(key string) uint64 { return v.GetUint64(key) }
+
func (v *Viper) GetUint64(key string) uint64 {
return cast.ToUint64(v.Get(key))
}
// GetFloat64 returns the value associated with the key as a float64.
func GetFloat64(key string) float64 { return v.GetFloat64(key) }
+
func (v *Viper) GetFloat64(key string) float64 {
return cast.ToFloat64(v.Get(key))
}
// GetTime returns the value associated with the key as time.
func GetTime(key string) time.Time { return v.GetTime(key) }
+
func (v *Viper) GetTime(key string) time.Time {
return cast.ToTime(v.Get(key))
}
// GetDuration returns the value associated with the key as a duration.
func GetDuration(key string) time.Duration { return v.GetDuration(key) }
+
func (v *Viper) GetDuration(key string) time.Duration {
return cast.ToDuration(v.Get(key))
}
// GetIntSlice returns the value associated with the key as a slice of int values.
func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
+
func (v *Viper) GetIntSlice(key string) []int {
return cast.ToIntSlice(v.Get(key))
}
// GetStringSlice returns the value associated with the key as a slice of strings.
func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
+
func (v *Viper) GetStringSlice(key string) []string {
return cast.ToStringSlice(v.Get(key))
}
// GetStringMap returns the value associated with the key as a map of interfaces.
func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) }
+
func (v *Viper) GetStringMap(key string) map[string]interface{} {
return cast.ToStringMap(v.Get(key))
}
// GetStringMapString returns the value associated with the key as a map of strings.
func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) }
+
func (v *Viper) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(v.Get(key))
}
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) }
+
func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
return cast.ToStringMapStringSlice(v.Get(key))
}
@@ -893,6 +1082,7 @@ func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
+
func (v *Viper) GetSizeInBytes(key string) uint {
sizeStr := cast.ToString(v.Get(key))
return parseSizeInBytes(sizeStr)
@@ -902,14 +1092,9 @@ func (v *Viper) GetSizeInBytes(key string) uint {
func UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
return v.UnmarshalKey(key, rawVal, opts...)
}
+
func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConfigOption) error {
- err := decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
-
- if err != nil {
- return err
- }
-
- return nil
+ return decode(v.Get(key), defaultDecoderConfig(rawVal, opts...))
}
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
@@ -917,14 +1102,9 @@ func (v *Viper) UnmarshalKey(key string, rawVal interface{}, opts ...DecoderConf
func Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
return v.Unmarshal(rawVal, opts...)
}
+
func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
- err := decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
-
- if err != nil {
- return err
- }
-
- return nil
+ return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
}
// defaultDecoderConfig returns default mapsstructure.DecoderConfig with suppot
@@ -959,22 +1139,18 @@ func decode(input interface{}, config *mapstructure.DecoderConfig) error {
func UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
return v.UnmarshalExact(rawVal, opts...)
}
+
func (v *Viper) UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error {
config := defaultDecoderConfig(rawVal, opts...)
config.ErrorUnused = true
- err := decode(v.AllSettings(), config)
-
- if err != nil {
- return err
- }
-
- return nil
+ return decode(v.AllSettings(), config)
}
// BindPFlags binds a full flag set to the configuration, using each flag's long
// name as the config key.
func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) }
+
func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
return v.BindFlagValues(pflagValueSet{flags})
}
@@ -982,17 +1158,21 @@ func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
-// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
-// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
-//
+// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
+// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }
+
func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {
+ if flag == nil {
+ return fmt.Errorf("flag for %q is nil", key)
+ }
return v.BindFlagValue(key, pflagValue{flag})
}
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) }
+
func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
flags.VisitAll(func(flag FlagValue) {
if err = v.BindFlagValue(flag.Name(), flag); err != nil {
@@ -1003,12 +1183,8 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
}
// BindFlagValue binds a specific key to a FlagValue.
-// Example (where serverCmd is a Cobra instance):
-//
-// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
-// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port"))
-//
func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }
+
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
if flag == nil {
return fmt.Errorf("flag for %q is nil", key)
@@ -1020,27 +1196,38 @@ func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
// BindEnv binds a Viper key to a ENV variable.
// ENV variables are case sensitive.
// If only a key is provided, it will use the env key matching the key, uppercased.
+// If more arguments are provided, they will represent the env variable names that
+// should bind to this key and will be taken in the specified order.
// EnvPrefix will be used when set when env name is not provided.
func BindEnv(input ...string) error { return v.BindEnv(input...) }
+
func (v *Viper) BindEnv(input ...string) error {
- var key, envkey string
if len(input) == 0 {
return fmt.Errorf("missing key to bind to")
}
- key = strings.ToLower(input[0])
+ key := strings.ToLower(input[0])
if len(input) == 1 {
- envkey = v.mergeWithEnvPrefix(key)
+ v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))
} else {
- envkey = input[1]
+ v.env[key] = append(v.env[key], input[1:]...)
}
- v.env[key] = envkey
-
return nil
}
+// MustBindEnv wraps BindEnv in a panic.
+// If there is an error binding an environment variable, MustBindEnv will
+// panic.
+func MustBindEnv(input ...string) { v.MustBindEnv(input...) }
+
+func (v *Viper) MustBindEnv(input ...string) {
+ if err := v.BindEnv(input...); err != nil {
+ panic(fmt.Sprintf("error while binding environment variable: %v", err))
+ }
+}
+
// Given a key, find the value.
//
// Viper will check to see if an alias exists first.
@@ -1085,7 +1272,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
- case "stringSlice":
+ case "stringSlice", "stringArray":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
@@ -1095,6 +1282,14 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
+ case "durationSlice":
+ s := strings.TrimPrefix(flag.ValueString(), "[")
+ s = strings.TrimSuffix(s, "]")
+ slice := strings.Split(s, ",")
+ return cast.ToDurationSlice(slice)
+ case "stringToString":
+ return stringToStringConv(flag.ValueString())
+
default:
return flag.ValueString()
}
@@ -1105,19 +1300,22 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
// Env override next
if v.automaticEnvApplied {
+ envKey := strings.Join(append(v.parents, lcaseKey), ".")
// even if it hasn't been registered, if automaticEnv is used,
// check any Get request
- if val, ok := v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); ok {
+ if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok {
return val
}
if nested && v.isPathShadowedInAutoEnv(path) != "" {
return nil
}
}
- envkey, exists := v.env[lcaseKey]
+ envkeys, exists := v.env[lcaseKey]
if exists {
- if val, ok := v.getEnv(envkey); ok {
- return val
+ for _, envkey := range envkeys {
+ if val, ok := v.getEnv(envkey); ok {
+ return val
+ }
}
}
if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {
@@ -1125,7 +1323,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
}
// Config file next
- val = v.searchMapWithPathPrefixes(v.config, path)
+ val = v.searchIndexableWithPathPrefixes(v.config, path)
if val != nil {
return val
}
@@ -1160,7 +1358,7 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
- case "stringSlice":
+ case "stringSlice", "stringArray":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
@@ -1170,6 +1368,13 @@ func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} {
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
+ case "stringToString":
+ return stringToStringConv(flag.ValueString())
+ case "durationSlice":
+ s := strings.TrimPrefix(flag.ValueString(), "[")
+ s = strings.TrimSuffix(s, "]")
+ slice := strings.Split(s, ",")
+ return cast.ToDurationSlice(slice)
default:
return flag.ValueString()
}
@@ -1189,18 +1394,44 @@ func readAsCSV(val string) ([]string, error) {
return csvReader.Read()
}
+// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79
+// alterations are: errors are swallowed, map[string]interface{} is returned in order to enable cast.ToStringMap
+func stringToStringConv(val string) interface{} {
+ val = strings.Trim(val, "[]")
+ // An empty string would cause an empty map
+ if len(val) == 0 {
+ return map[string]interface{}{}
+ }
+ r := csv.NewReader(strings.NewReader(val))
+ ss, err := r.Read()
+ if err != nil {
+ return nil
+ }
+ out := make(map[string]interface{}, len(ss))
+ for _, pair := range ss {
+ kv := strings.SplitN(pair, "=", 2)
+ if len(kv) != 2 {
+ return nil
+ }
+ out[kv[0]] = kv[1]
+ }
+ return out
+}
+
// IsSet checks to see if the key has been set in any of the data locations.
// IsSet is case-insensitive for a key.
func IsSet(key string) bool { return v.IsSet(key) }
+
func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, false)
return val != nil
}
-// AutomaticEnv has Viper check ENV variables for all.
-// keys set in config, default & flags
+// AutomaticEnv makes Viper check if environment variables match any of the existing keys
+// (config, default or flags). If matching env vars are found, they are loaded into Viper.
func AutomaticEnv() { v.AutomaticEnv() }
+
func (v *Viper) AutomaticEnv() {
v.automaticEnvApplied = true
}
@@ -1209,6 +1440,7 @@ func (v *Viper) AutomaticEnv() {
// Useful for mapping an environmental variable to a key that does
// not match it.
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
+
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
v.envKeyReplacer = r
}
@@ -1216,6 +1448,7 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) }
+
func (v *Viper) RegisterAlias(alias string, key string) {
v.registerAlias(alias, strings.ToLower(key))
}
@@ -1248,14 +1481,15 @@ func (v *Viper) registerAlias(alias string, key string) {
v.aliases[alias] = key
}
} else {
- jww.WARN.Println("Creating circular reference alias", alias, key, v.realKey(key))
+ v.logger.Warn("creating circular reference alias", "alias", alias, "key", key, "real_key", v.realKey(key))
}
}
func (v *Viper) realKey(key string) string {
newkey, exists := v.aliases[key]
if exists {
- jww.DEBUG.Println("Alias", key, "to", newkey)
+ v.logger.Debug("key is an alias", "alias", key, "to", newkey)
+
return v.realKey(newkey)
}
return key
@@ -1263,18 +1497,22 @@ func (v *Viper) realKey(key string) string {
// InConfig checks to see if the given key (or an alias) is in the config file.
func InConfig(key string) bool { return v.InConfig(key) }
-func (v *Viper) InConfig(key string) bool {
- // if the requested key is an alias, then return the proper key
- key = v.realKey(key)
- _, exists := v.config[key]
- return exists
+func (v *Viper) InConfig(key string) bool {
+ lcaseKey := strings.ToLower(key)
+
+ // if the requested key is an alias, then return the proper key
+ lcaseKey = v.realKey(lcaseKey)
+ path := strings.Split(lcaseKey, v.keyDelim)
+
+ return v.searchIndexableWithPathPrefixes(v.config, path) != nil
}
// SetDefault sets the default value for this key.
// SetDefault is case-insensitive for a key.
// Default only used when no value is provided by the user via flag, config or ENV.
func SetDefault(key string, value interface{}) { v.SetDefault(key, value) }
+
func (v *Viper) SetDefault(key string, value interface{}) {
// If alias passed in, then set the proper default
key = v.realKey(strings.ToLower(key))
@@ -1285,9 +1523,7 @@ func (v *Viper) SetDefault(key string, value interface{}) {
deepestMap := deepSearch(v.defaults, path[0:len(path)-1])
// set innermost value
- v.mu.Lock()
deepestMap[lastKey] = value
- v.mu.Unlock()
}
// Set sets the value for the key in the override register.
@@ -1295,6 +1531,7 @@ func (v *Viper) SetDefault(key string, value interface{}) {
// Will be used instead of values obtained via
// flags, config file, ENV, default, or key/value store.
func Set(key string, value interface{}) { v.Set(key, value) }
+
func (v *Viper) Set(key string, value interface{}) {
// If alias passed in, then set the proper override
key = v.realKey(strings.ToLower(key))
@@ -1305,16 +1542,15 @@ func (v *Viper) Set(key string, value interface{}) {
deepestMap := deepSearch(v.override, path[0:len(path)-1])
// set innermost value
- v.mu.Lock()
deepestMap[lastKey] = value
- v.mu.Unlock()
}
// ReadInConfig will discover and load the configuration file from disk
// and key/value stores, searching in one of the defined paths.
func ReadInConfig() error { return v.ReadInConfig() }
+
func (v *Viper) ReadInConfig() error {
- jww.INFO.Println("Attempting to read in config file")
+ v.logger.Info("attempting to read in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
@@ -1324,7 +1560,7 @@ func (v *Viper) ReadInConfig() error {
return UnsupportedConfigError(v.getConfigType())
}
- jww.DEBUG.Println("Reading file: ", filename)
+ v.logger.Debug("reading file", "file", filename)
file, err := afero.ReadFile(v.fs, filename)
if err != nil {
return err
@@ -1343,8 +1579,9 @@ func (v *Viper) ReadInConfig() error {
// MergeInConfig merges a new configuration with an existing config.
func MergeInConfig() error { return v.MergeInConfig() }
+
func (v *Viper) MergeInConfig() error {
- jww.INFO.Println("Attempting to merge in config file")
+ v.logger.Info("attempting to merge in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
@@ -1365,6 +1602,7 @@ func (v *Viper) MergeInConfig() error {
// ReadConfig will read a configuration file, setting existing keys to nil if the
// key does not exist in the file.
func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
+
func (v *Viper) ReadConfig(in io.Reader) error {
v.config = make(map[string]interface{})
return v.unmarshalReader(in, v.config)
@@ -1372,6 +1610,7 @@ func (v *Viper) ReadConfig(in io.Reader) error {
// MergeConfig merges a new configuration with an existing config.
func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
+
func (v *Viper) MergeConfig(in io.Reader) error {
cfg := make(map[string]interface{})
if err := v.unmarshalReader(in, cfg); err != nil {
@@ -1383,6 +1622,7 @@ func (v *Viper) MergeConfig(in io.Reader) error {
// MergeConfigMap merges the configuration from the map given with an existing config.
// Note that the map given may be modified.
func MergeConfigMap(cfg map[string]interface{}) error { return v.MergeConfigMap(cfg) }
+
func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
if v.config == nil {
v.config = make(map[string]interface{})
@@ -1394,6 +1634,7 @@ func (v *Viper) MergeConfigMap(cfg map[string]interface{}) error {
// WriteConfig writes the current configuration to a file.
func WriteConfig() error { return v.WriteConfig() }
+
func (v *Viper) WriteConfig() error {
filename, err := v.getConfigFile()
if err != nil {
@@ -1404,6 +1645,7 @@ func (v *Viper) WriteConfig() error {
// SafeWriteConfig writes current configuration to file only if the file does not exist.
func SafeWriteConfig() error { return v.SafeWriteConfig() }
+
func (v *Viper) SafeWriteConfig() error {
if len(v.configPaths) < 1 {
return errors.New("missing configuration for 'configPath'")
@@ -1413,12 +1655,14 @@ func (v *Viper) SafeWriteConfig() error {
// WriteConfigAs writes current configuration to a given filename.
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }
+
func (v *Viper) WriteConfigAs(filename string) error {
return v.writeConfig(filename, true)
}
// SafeWriteConfigAs writes current configuration to a given filename if it does not exist.
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }
+
func (v *Viper) SafeWriteConfigAs(filename string) error {
alreadyExists, err := afero.Exists(v.fs, filename)
if alreadyExists && err == nil {
@@ -1428,12 +1672,20 @@ func (v *Viper) SafeWriteConfigAs(filename string) error {
}
func (v *Viper) writeConfig(filename string, force bool) error {
- jww.INFO.Println("Attempting to write configuration to file.")
+ v.logger.Info("attempting to write configuration to file")
+
+ var configType string
+
ext := filepath.Ext(filename)
- if len(ext) <= 1 {
- return fmt.Errorf("filename: %s requires valid extension", filename)
+ if ext != "" && ext != filepath.Base(filename) {
+ configType = ext[1:]
+ } else {
+ configType = v.configType
}
- configType := ext[1:]
+ if configType == "" {
+ return fmt.Errorf("config type could not be determined for %s", filename)
+ }
+
if !stringInSlice(configType, SupportedExts) {
return UnsupportedConfigError(configType)
}
@@ -1462,85 +1714,17 @@ func (v *Viper) writeConfig(filename string, force bool) error {
func unmarshalReader(in io.Reader, c map[string]interface{}) error {
return v.unmarshalReader(in, c)
}
+
func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
- v.mu.Lock()
- defer v.mu.Unlock()
buf := new(bytes.Buffer)
- if _, err := buf.ReadFrom(in); err != nil {
- return err
- }
+ buf.ReadFrom(in)
- switch strings.ToLower(v.getConfigType()) {
- case "yaml", "yml":
- if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
- return ConfigParseError{err}
- }
-
- case "json":
- if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
- return ConfigParseError{err}
- }
-
- case "hcl":
- obj, err := hcl.Parse(buf.String())
+ switch format := strings.ToLower(v.getConfigType()); format {
+ case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env":
+ err := v.decoderRegistry.Decode(format, buf.Bytes(), c)
if err != nil {
return ConfigParseError{err}
}
- if err = hcl.DecodeObject(&c, obj); err != nil {
- return ConfigParseError{err}
- }
-
- case "toml":
- tree, err := toml.LoadReader(buf)
- if err != nil {
- return ConfigParseError{err}
- }
- tmap := tree.ToMap()
- for k, v := range tmap {
- c[k] = v
- }
-
- case "dotenv", "env":
- env, err := gotenv.StrictParse(buf)
- if err != nil {
- return ConfigParseError{err}
- }
- for k, v := range env {
- c[k] = v
- }
-
- case "properties", "props", "prop":
- v.properties = properties.NewProperties()
- var err error
- if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
- return ConfigParseError{err}
- }
- for _, key := range v.properties.Keys() {
- value, _ := v.properties.Get(key)
- // recursively build nested maps
- path := strings.Split(key, ".")
- lastKey := strings.ToLower(path[len(path)-1])
- deepestMap := deepSearch(c, path[0:len(path)-1])
- // set innermost value
- deepestMap[lastKey] = value
- }
-
- case "ini":
- cfg := ini.Empty()
- err := cfg.Append(buf.Bytes())
- if err != nil {
- return ConfigParseError{err}
- }
- sections := cfg.Sections()
- for i := 0; i < len(sections); i++ {
- section := sections[i]
- keys := section.Keys()
- for j := 0; j < len(keys); j++ {
- key := keys[j]
- value := cfg.Section(section.Name()).Key(key.Name()).String()
- c[section.Name()+"."+key.Name()] = value
- }
- }
}
insensitiviseMap(c)
@@ -1551,92 +1735,16 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
func (v *Viper) marshalWriter(f afero.File, configType string) error {
c := v.AllSettings()
switch configType {
- case "json":
- b, err := json.MarshalIndent(c, "", " ")
+ case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env":
+ b, err := v.encoderRegistry.Encode(configType, c)
if err != nil {
return ConfigMarshalError{err}
}
+
_, err = f.WriteString(string(b))
if err != nil {
return ConfigMarshalError{err}
}
-
- case "hcl":
- b, err := json.Marshal(c)
- if err != nil {
- return ConfigMarshalError{err}
- }
- ast, err := hcl.Parse(string(b))
- if err != nil {
- return ConfigMarshalError{err}
- }
- err = printer.Fprint(f, ast.Node)
- if err != nil {
- return ConfigMarshalError{err}
- }
-
- case "prop", "props", "properties":
- if v.properties == nil {
- v.properties = properties.NewProperties()
- }
- p := v.properties
- for _, key := range v.AllKeys() {
- _, _, err := p.Set(key, v.GetString(key))
- if err != nil {
- return ConfigMarshalError{err}
- }
- }
- _, err := p.WriteComment(f, "#", properties.UTF8)
- if err != nil {
- return ConfigMarshalError{err}
- }
-
- case "dotenv", "env":
- lines := []string{}
- for _, key := range v.AllKeys() {
- envName := strings.ToUpper(strings.Replace(key, ".", "_", -1))
- val := v.Get(key)
- lines = append(lines, fmt.Sprintf("%v=%v", envName, val))
- }
- s := strings.Join(lines, "\n")
- if _, err := f.WriteString(s); err != nil {
- return ConfigMarshalError{err}
- }
-
- case "toml":
- t, err := toml.TreeFromMap(c)
- if err != nil {
- return ConfigMarshalError{err}
- }
- s := t.String()
- if _, err := f.WriteString(s); err != nil {
- return ConfigMarshalError{err}
- }
-
- case "yaml", "yml":
- b, err := yaml.Marshal(c)
- if err != nil {
- return ConfigMarshalError{err}
- }
- if _, err = f.WriteString(string(b)); err != nil {
- return ConfigMarshalError{err}
- }
-
- case "ini":
- keys := v.AllKeys()
- cfg := ini.Empty()
- ini.PrettyFormat = false
- for i := 0; i < len(keys); i++ {
- key := keys[i]
- lastSep := strings.LastIndex(key, ".")
- sectionName := key[:(lastSep)]
- keyName := key[(lastSep + 1):]
- if sectionName == "default" {
- sectionName = ""
- }
- cfg.Section(sectionName).Key(keyName).SetValue(Get(key).(string))
- }
- cfg.WriteTo(f)
}
return nil
}
@@ -1653,7 +1761,8 @@ func keyExists(k string, m map[string]interface{}) string {
}
func castToMapStringInterface(
- src map[interface{}]interface{}) map[string]interface{} {
+ src map[interface{}]interface{},
+) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[fmt.Sprintf("%v", k)] = v
@@ -1661,6 +1770,14 @@ func castToMapStringInterface(
return tgt
}
+func castMapStringSliceToMapInterface(src map[string][]string) map[string]interface{} {
+ tgt := map[string]interface{}{}
+ for k, v := range src {
+ tgt[k] = v
+ }
+ return tgt
+}
+
func castMapStringToMapInterface(src map[string]string) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
@@ -1683,11 +1800,12 @@ func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{}
// deep. Both map types are supported as there is a go-yaml fork that uses
// `map[string]interface{}` instead.
func mergeMaps(
- src, tgt map[string]interface{}, itgt map[interface{}]interface{}) {
+ src, tgt map[string]interface{}, itgt map[interface{}]interface{},
+) {
for sk, sv := range src {
tk := keyExists(sk, tgt)
if tk == "" {
- jww.TRACE.Printf("tk=\"\", tgt[%s]=%v", sk, sv)
+ v.logger.Trace("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv)
tgt[sk] = sv
if itgt != nil {
itgt[sk] = sv
@@ -1697,7 +1815,7 @@ func mergeMaps(
tv, ok := tgt[tk]
if !ok {
- jww.TRACE.Printf("tgt[%s] != ok, tgt[%s]=%v", tk, sk, sv)
+ v.logger.Trace("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv)
tgt[sk] = sv
if itgt != nil {
itgt[sk] = sv
@@ -1707,28 +1825,52 @@ func mergeMaps(
svType := reflect.TypeOf(sv)
tvType := reflect.TypeOf(tv)
- if svType != tvType {
- jww.ERROR.Printf(
- "svType != tvType; key=%s, st=%v, tt=%v, sv=%v, tv=%v",
- sk, svType, tvType, sv, tv)
- continue
- }
- jww.TRACE.Printf("processing key=%s, st=%v, tt=%v, sv=%v, tv=%v",
- sk, svType, tvType, sv, tv)
+ v.logger.Trace(
+ "processing",
+ "key", sk,
+ "st", svType,
+ "tt", tvType,
+ "sv", sv,
+ "tv", tv,
+ )
switch ttv := tv.(type) {
case map[interface{}]interface{}:
- jww.TRACE.Printf("merging maps (must convert)")
- tsv := sv.(map[interface{}]interface{})
+ v.logger.Trace("merging maps (must convert)")
+ tsv, ok := sv.(map[interface{}]interface{})
+ if !ok {
+ v.logger.Error(
+ "Could not cast sv to map[interface{}]interface{}",
+ "key", sk,
+ "st", svType,
+ "tt", tvType,
+ "sv", sv,
+ "tv", tv,
+ )
+ continue
+ }
+
ssv := castToMapStringInterface(tsv)
stv := castToMapStringInterface(ttv)
mergeMaps(ssv, stv, ttv)
case map[string]interface{}:
- jww.TRACE.Printf("merging maps")
- mergeMaps(sv.(map[string]interface{}), ttv, nil)
+ v.logger.Trace("merging maps")
+ tsv, ok := sv.(map[string]interface{})
+ if !ok {
+ v.logger.Error(
+ "Could not cast sv to map[string]interface{}",
+ "key", sk,
+ "st", svType,
+ "tt", tvType,
+ "sv", sv,
+ "tv", tv,
+ )
+ continue
+ }
+ mergeMaps(tsv, ttv, nil)
default:
- jww.TRACE.Printf("setting value")
+ v.logger.Trace("setting value")
tgt[tk] = sv
if itgt != nil {
itgt[tk] = sv
@@ -1740,6 +1882,7 @@ func mergeMaps(
// ReadRemoteConfig attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
+
func (v *Viper) ReadRemoteConfig() error {
return v.getKeyValueConfig()
}
@@ -1756,15 +1899,23 @@ func (v *Viper) WatchRemoteConfigOnChannel() error {
// Retrieve the first found remote configuration.
func (v *Viper) getKeyValueConfig() error {
if RemoteConfig == nil {
- return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/ShaleApps/viper/remote'")
+ return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'")
+ }
+
+ if len(v.remoteProviders) == 0 {
+ return RemoteConfigError("No Remote Providers")
}
for _, rp := range v.remoteProviders {
val, err := v.getRemoteConfig(rp)
if err != nil {
+ v.logger.Error(fmt.Errorf("get remote config: %w", err).Error())
+
continue
}
+
v.kvstore = val
+
return nil
}
return RemoteConfigError("No Files Found")
@@ -1781,6 +1932,10 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}
// Retrieve the first found remote configuration.
func (v *Viper) watchKeyValueConfigOnChannel() error {
+ if len(v.remoteProviders) == 0 {
+ return RemoteConfigError("No Remote Providers")
+ }
+
for _, rp := range v.remoteProviders {
respc, _ := RemoteConfig.WatchChannel(rp)
// Todo: Add quit channel
@@ -1798,9 +1953,15 @@ func (v *Viper) watchKeyValueConfigOnChannel() error {
// Retrieve the first found remote configuration.
func (v *Viper) watchKeyValueConfig() error {
+ if len(v.remoteProviders) == 0 {
+ return RemoteConfigError("No Remote Providers")
+ }
+
for _, rp := range v.remoteProviders {
val, err := v.watchRemoteConfig(rp)
if err != nil {
+ v.logger.Error(fmt.Errorf("watch remote config: %w", err).Error())
+
continue
}
v.kvstore = val
@@ -1821,13 +1982,14 @@ func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface
// AllKeys returns all keys holding a value, regardless of where they are set.
// Nested keys are returned with a v.keyDelim separator
func AllKeys() []string { return v.AllKeys() }
+
func (v *Viper) AllKeys() []string {
m := map[string]bool{}
// add all paths, by order of descending priority to ensure correct shadowing
m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "")
m = v.flattenAndMergeMap(m, v.override, "")
m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags))
- m = v.mergeFlatMap(m, castMapStringToMapInterface(v.env))
+ m = v.mergeFlatMap(m, castMapStringSliceToMapInterface(v.env))
m = v.flattenAndMergeMap(m, v.config, "")
m = v.flattenAndMergeMap(m, v.kvstore, "")
m = v.flattenAndMergeMap(m, v.defaults, "")
@@ -1842,9 +2004,10 @@ func (v *Viper) AllKeys() []string {
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
-// - each path is merged into a single key string, delimited with v.keyDelim
-// - if a path is shadowed by an earlier value in the initial shadow map,
-// it is skipped.
+// - each path is merged into a single key string, delimited with v.keyDelim
+// - if a path is shadowed by an earlier value in the initial shadow map,
+// it is skipped.
+//
// The resulting set of paths is merged to the given shadow set at the same time.
func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
if shadow != nil && prefix != "" && shadow[prefix] {
@@ -1901,9 +2064,8 @@ outer:
// AllSettings merges all settings and returns them as a map[string]interface{}.
func AllSettings() map[string]interface{} { return v.AllSettings() }
+
func (v *Viper) AllSettings() map[string]interface{} {
- //v.mu.Lock()
- //defer v.mu.Unlock()
m := map[string]interface{}{}
// start from the list of keys, and construct the map one value at a time
for _, k := range v.AllKeys() {
@@ -1924,6 +2086,7 @@ func (v *Viper) AllSettings() map[string]interface{} {
// SetFs sets the filesystem to use to read configuration.
func SetFs(fs afero.Fs) { v.SetFs(fs) }
+
func (v *Viper) SetFs(fs afero.Fs) {
v.fs = fs
}
@@ -1931,6 +2094,7 @@ func (v *Viper) SetFs(fs afero.Fs) {
// SetConfigName sets name for the config file.
// Does not include extension.
func SetConfigName(in string) { v.SetConfigName(in) }
+
func (v *Viper) SetConfigName(in string) {
if in != "" {
v.configName = in
@@ -1941,6 +2105,7 @@ func (v *Viper) SetConfigName(in string) {
// SetConfigType sets the type of the configuration returned by the
// remote source, e.g. "json".
func SetConfigType(in string) { v.SetConfigType(in) }
+
func (v *Viper) SetConfigType(in string) {
if in != "" {
v.configType = in
@@ -1949,10 +2114,18 @@ func (v *Viper) SetConfigType(in string) {
// SetConfigPermissions sets the permissions for the config file.
func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) }
+
func (v *Viper) SetConfigPermissions(perm os.FileMode) {
v.configPermissions = perm.Perm()
}
+// IniLoadOptions sets the load options for ini parsing.
+func IniLoadOptions(in ini.LoadOptions) Option {
+ return optionFunc(func(v *Viper) {
+ v.iniLoadOptions = in
+ })
+}
+
func (v *Viper) getConfigType() string {
if v.configType != "" {
return v.configType
@@ -1983,46 +2156,19 @@ func (v *Viper) getConfigFile() (string, error) {
return v.configFile, nil
}
-func (v *Viper) searchInPath(in string) (filename string) {
- jww.DEBUG.Println("Searching for config in ", in)
- for _, ext := range SupportedExts {
- jww.DEBUG.Println("Checking for", filepath.Join(in, v.configName+"."+ext))
- if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
- jww.DEBUG.Println("Found: ", filepath.Join(in, v.configName+"."+ext))
- return filepath.Join(in, v.configName+"."+ext)
- }
- }
-
- if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
- return filepath.Join(in, v.configName)
- }
-
- return ""
-}
-
-// Search all configPaths for any config file.
-// Returns the first path that exists (and is a config file).
-func (v *Viper) findConfigFile() (string, error) {
- jww.INFO.Println("Searching for config in ", v.configPaths)
-
- for _, cp := range v.configPaths {
- file := v.searchInPath(cp)
- if file != "" {
- return file, nil
- }
- }
- return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
-}
-
// Debug prints all configuration registries for debugging
// purposes.
-func Debug() { v.Debug() }
-func (v *Viper) Debug() {
- fmt.Printf("Aliases:\n%#v\n", v.aliases)
- fmt.Printf("Override:\n%#v\n", v.override)
- fmt.Printf("PFlags:\n%#v\n", v.pflags)
- fmt.Printf("Env:\n%#v\n", v.env)
- fmt.Printf("Key/Value Store:\n%#v\n", v.kvstore)
- fmt.Printf("Config:\n%#v\n", v.config)
- fmt.Printf("Defaults:\n%#v\n", v.defaults)
+func Debug() { v.Debug() }
+func DebugTo(w io.Writer) { v.DebugTo(w) }
+
+func (v *Viper) Debug() { v.DebugTo(os.Stdout) }
+
+func (v *Viper) DebugTo(w io.Writer) {
+ fmt.Fprintf(w, "Aliases:\n%#v\n", v.aliases)
+ fmt.Fprintf(w, "Override:\n%#v\n", v.override)
+ fmt.Fprintf(w, "PFlags:\n%#v\n", v.pflags)
+ fmt.Fprintf(w, "Env:\n%#v\n", v.env)
+ fmt.Fprintf(w, "Key/Value Store:\n%#v\n", v.kvstore)
+ fmt.Fprintf(w, "Config:\n%#v\n", v.config)
+ fmt.Fprintf(w, "Defaults:\n%#v\n", v.defaults)
}
diff --git a/viper_go1_15.go b/viper_go1_15.go
new file mode 100644
index 0000000..19a771c
--- /dev/null
+++ b/viper_go1_15.go
@@ -0,0 +1,57 @@
+//go:build !go1.16 || !finder
+// +build !go1.16 !finder
+
+package viper
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/spf13/afero"
+)
+
+// Search all configPaths for any config file.
+// Returns the first path that exists (and is a config file).
+func (v *Viper) findConfigFile() (string, error) {
+ v.logger.Info("searching for config in paths", "paths", v.configPaths)
+
+ for _, cp := range v.configPaths {
+ file := v.searchInPath(cp)
+ if file != "" {
+ return file, nil
+ }
+ }
+ return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
+}
+
+func (v *Viper) searchInPath(in string) (filename string) {
+ v.logger.Debug("searching for config in path", "path", in)
+ for _, ext := range SupportedExts {
+ v.logger.Debug("checking if file exists", "file", filepath.Join(in, v.configName+"."+ext))
+ if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
+ v.logger.Debug("found file", "file", filepath.Join(in, v.configName+"."+ext))
+ return filepath.Join(in, v.configName+"."+ext)
+ }
+ }
+
+ if v.configType != "" {
+ if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
+ return filepath.Join(in, v.configName)
+ }
+ }
+
+ return ""
+}
+
+// Check if file Exists
+func exists(fs afero.Fs, path string) (bool, error) {
+ stat, err := fs.Stat(path)
+ if err == nil {
+ return !stat.IsDir(), nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
diff --git a/viper_go1_16.go b/viper_go1_16.go
new file mode 100644
index 0000000..e10172f
--- /dev/null
+++ b/viper_go1_16.go
@@ -0,0 +1,32 @@
+//go:build go1.16 && finder
+// +build go1.16,finder
+
+package viper
+
+import (
+ "fmt"
+
+ "github.com/spf13/afero"
+)
+
+// Search all configPaths for any config file.
+// Returns the first path that exists (and is a config file).
+func (v *Viper) findConfigFile() (string, error) {
+ finder := finder{
+ paths: v.configPaths,
+ fileNames: []string{v.configName},
+ extensions: SupportedExts,
+ withoutExtension: v.configType != "",
+ }
+
+ file, err := finder.Find(afero.NewIOFS(v.fs))
+ if err != nil {
+ return "", err
+ }
+
+ if file == "" {
+ return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
+ }
+
+ return file, nil
+}
diff --git a/viper_test.go b/viper_test.go
index fcd1b0e..b867337 100644
--- a/viper_test.go
+++ b/viper_test.go
@@ -9,7 +9,7 @@ import (
"bytes"
"encoding/json"
"io"
- "io/ioutil"
+ "io/ioutil" //nolint:staticcheck
"os"
"os/exec"
"path"
@@ -26,27 +26,28 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
"github.com/spf13/cast"
-
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+
+ "github.com/spf13/viper/internal/testutil"
)
-var yamlExample = []byte(`Hacker: true
-name: steve
-hobbies:
-- skateboarding
-- snowboarding
-- go
-clothing:
- jacket: leather
- trousers: denim
- pants:
- size: large
-age: 35
-eyes : brown
-beard: true
-`)
+// var yamlExample = []byte(`Hacker: true
+// name: steve
+// hobbies:
+// - skateboarding
+// - snowboarding
+// - go
+// clothing:
+// jacket: leather
+// trousers: denim
+// pants:
+// size: large
+// age: 35
+// eyes : brown
+// beard: true
+// `)
var yamlExampleWithExtras = []byte(`Existing: true
Bogus: true
@@ -262,13 +263,13 @@ func initDirs(t *testing.T) (string, string, func()) {
require.Nil(t, err)
for _, dir := range testDirs {
- err = os.Mkdir(dir, 0750)
+ err = os.Mkdir(dir, 0o750)
assert.Nil(t, err)
err = ioutil.WriteFile(
path.Join(dir, config+".toml"),
[]byte("key = \"value is "+dir+"\"\n"),
- 0640)
+ 0o640)
assert.Nil(t, err)
}
@@ -300,47 +301,192 @@ func (s *stringValue) String() string {
return string(*s)
}
-func TestBasics(t *testing.T) {
- SetConfigFile("/tmp/config.yaml")
- filename, err := v.getConfigFile()
- assert.Equal(t, "/tmp/config.yaml", filename)
- assert.NoError(t, err)
+func TestGetConfigFile(t *testing.T) {
+ t.Run("config file set", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc/viper")
+ v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+
+ filename, err := v.getConfigFile()
+ assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename)
+ assert.NoError(t, err)
+ })
+
+ t.Run("find file", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc/viper")
+
+ filename, err := v.getConfigFile()
+ assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename)
+ assert.NoError(t, err)
+ })
+
+ t.Run("find files only", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/config"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/config/config.yaml"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc")
+ v.AddConfigPath("/etc/config")
+
+ filename, err := v.getConfigFile()
+ assert.Equal(t, testutil.AbsFilePath(t, "/etc/config/config.yaml"), filename)
+ assert.NoError(t, err)
+ })
+
+ t.Run("precedence", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/home/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/home/viper/config.zml"))
+ require.NoError(t, err)
+
+ err = fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.bml"))
+ require.NoError(t, err)
+
+ err = fs.Mkdir(testutil.AbsFilePath(t, "/var/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/var/viper/config.yaml"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/home/viper")
+ v.AddConfigPath("/etc/viper")
+ v.AddConfigPath("/var/viper")
+
+ filename, err := v.getConfigFile()
+ assert.Equal(t, testutil.AbsFilePath(t, "/var/viper/config.yaml"), filename)
+ assert.NoError(t, err)
+ })
+
+ t.Run("without extension", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc/viper")
+ v.SetConfigName(".dotfilenoext")
+ v.SetConfigType("yaml")
+
+ filename, err := v.getConfigFile()
+ assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"), filename)
+ assert.NoError(t, err)
+ })
+
+ t.Run("without extension and config type", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ _, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"))
+ require.NoError(t, err)
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc/viper")
+ v.SetConfigName(".dotfilenoext")
+
+ _, err = v.getConfigFile()
+ // unless config type is set, files without extension
+ // are not considered
+ assert.Error(t, err)
+ })
}
-func TestSearchInPath(t *testing.T) {
- filename := ".dotfilenoext"
- path := "/tmp"
- file := filepath.Join(path, filename)
- SetConfigName(filename)
- AddConfigPath(path)
- _, createErr := v.fs.Create(file)
- defer func() {
- _ = v.fs.Remove(file)
- }()
- assert.NoError(t, createErr)
- filename, err := v.getConfigFile()
- assert.Equal(t, file, filename)
- assert.NoError(t, err)
-}
+func TestReadInConfig(t *testing.T) {
+ t.Run("config file set", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
-func TestSearchInPath_FilesOnly(t *testing.T) {
- fs := afero.NewMemMapFs()
+ err := fs.Mkdir("/etc/viper", 0o777)
+ require.NoError(t, err)
- err := fs.Mkdir("/tmp/config", 0777)
- require.NoError(t, err)
+ file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+ require.NoError(t, err)
- _, err = fs.Create("/tmp/config/config.yaml")
- require.NoError(t, err)
+ _, err = file.Write([]byte(`key: value`))
+ require.NoError(t, err)
- v := New()
+ file.Close()
- v.SetFs(fs)
- v.AddConfigPath("/tmp")
- v.AddConfigPath("/tmp/config")
+ v := New()
- filename, err := v.getConfigFile()
- assert.Equal(t, "/tmp/config/config.yaml", filename)
- assert.NoError(t, err)
+ v.SetFs(fs)
+ v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+
+ err = v.ReadInConfig()
+ require.NoError(t, err)
+
+ assert.Equal(t, "value", v.Get("key"))
+ })
+
+ t.Run("find file", func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
+ require.NoError(t, err)
+
+ file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
+ require.NoError(t, err)
+
+ _, err = file.Write([]byte(`key: value`))
+ require.NoError(t, err)
+
+ file.Close()
+
+ v := New()
+
+ v.SetFs(fs)
+ v.AddConfigPath("/etc/viper")
+
+ err = v.ReadInConfig()
+ require.NoError(t, err)
+
+ assert.Equal(t, "value", v.Get("key"))
+ })
}
func TestDefault(t *testing.T) {
@@ -363,7 +509,9 @@ func TestUnmarshaling(t *testing.T) {
unmarshalReader(r, v.config)
assert.True(t, InConfig("name"))
+ assert.True(t, InConfig("clothing.jacket"))
assert.False(t, InConfig("state"))
+ assert.False(t, InConfig("clothing.hat"))
assert.Equal(t, "steve", Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies"))
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing"))
@@ -469,11 +617,12 @@ func TestEnv(t *testing.T) {
initJSON()
BindEnv("id")
- BindEnv("f", "FOOD")
+ BindEnv("f", "FOOD", "OLD_FOOD")
- os.Setenv("ID", "13")
- os.Setenv("FOOD", "apple")
- os.Setenv("NAME", "crunk")
+ testutil.Setenv(t, "ID", "13")
+ testutil.Setenv(t, "FOOD", "apple")
+ testutil.Setenv(t, "OLD_FOOD", "banana")
+ testutil.Setenv(t, "NAME", "crunk")
assert.Equal(t, "13", Get("id"))
assert.Equal(t, "apple", Get("f"))
@@ -484,18 +633,23 @@ func TestEnv(t *testing.T) {
assert.Equal(t, "crunk", Get("name"))
}
+func TestMultipleEnv(t *testing.T) {
+ initJSON()
+
+ BindEnv("f", "FOOD", "OLD_FOOD")
+
+ testutil.Setenv(t, "OLD_FOOD", "banana")
+
+ assert.Equal(t, "banana", Get("f"))
+}
+
func TestEmptyEnv(t *testing.T) {
initJSON()
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
- os.Unsetenv("type")
- os.Unsetenv("TYPE")
- os.Unsetenv("name")
- os.Unsetenv("NAME")
-
- os.Setenv("TYPE", "")
+ testutil.Setenv(t, "TYPE", "")
assert.Equal(t, "donut", Get("type"))
assert.Equal(t, "Cake", Get("name"))
@@ -509,12 +663,7 @@ func TestEmptyEnv_Allowed(t *testing.T) {
BindEnv("type") // Empty environment variable
BindEnv("name") // Bound, but not set environment variable
- os.Unsetenv("type")
- os.Unsetenv("TYPE")
- os.Unsetenv("name")
- os.Unsetenv("NAME")
-
- os.Setenv("TYPE", "")
+ testutil.Setenv(t, "TYPE", "")
assert.Equal(t, "", Get("type"))
assert.Equal(t, "Cake", Get("name"))
@@ -527,9 +676,9 @@ func TestEnvPrefix(t *testing.T) {
BindEnv("id")
BindEnv("f", "FOOD") // not using prefix
- os.Setenv("FOO_ID", "13")
- os.Setenv("FOOD", "apple")
- os.Setenv("FOO_NAME", "crunk")
+ testutil.Setenv(t, "FOO_ID", "13")
+ testutil.Setenv(t, "FOOD", "apple")
+ testutil.Setenv(t, "FOO_NAME", "crunk")
assert.Equal(t, "13", Get("id"))
assert.Equal(t, "apple", Get("f"))
@@ -544,7 +693,9 @@ func TestAutoEnv(t *testing.T) {
Reset()
AutomaticEnv()
- os.Setenv("FOO_BAR", "13")
+
+ testutil.Setenv(t, "FOO_BAR", "13")
+
assert.Equal(t, "13", Get("foo_bar"))
}
@@ -553,7 +704,9 @@ func TestAutoEnvWithPrefix(t *testing.T) {
AutomaticEnv()
SetEnvPrefix("Baz")
- os.Setenv("BAZ_BAR", "13")
+
+ testutil.Setenv(t, "BAZ_BAR", "13")
+
assert.Equal(t, "13", Get("bar"))
}
@@ -561,7 +714,8 @@ func TestSetEnvKeyReplacer(t *testing.T) {
Reset()
AutomaticEnv()
- os.Setenv("REFRESH_INTERVAL", "30s")
+
+ testutil.Setenv(t, "REFRESH_INTERVAL", "30s")
replacer := strings.NewReplacer("-", "_")
SetEnvKeyReplacer(replacer)
@@ -573,11 +727,30 @@ func TestEnvKeyReplacer(t *testing.T) {
v := NewWithOptions(EnvKeyReplacer(strings.NewReplacer("-", "_")))
v.AutomaticEnv()
- _ = os.Setenv("REFRESH_INTERVAL", "30s")
+
+ testutil.Setenv(t, "REFRESH_INTERVAL", "30s")
assert.Equal(t, "30s", v.Get("refresh-interval"))
}
+func TestEnvSubConfig(t *testing.T) {
+ initYAML()
+
+ v.AutomaticEnv()
+
+ v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
+
+ testutil.Setenv(t, "CLOTHING_PANTS_SIZE", "small")
+ subv := v.Sub("clothing").Sub("pants")
+ assert.Equal(t, "small", subv.Get("size"))
+
+ // again with EnvPrefix
+ v.SetEnvPrefix("foo") // will be uppercased automatically
+ subWithPrefix := v.Sub("clothing").Sub("pants")
+ testutil.Setenv(t, "FOO_CLOTHING_PANTS_SIZE", "large")
+ assert.Equal(t, "large", subWithPrefix.Get("size"))
+}
+
func TestAllKeys(t *testing.T) {
initConfigs()
@@ -676,7 +849,8 @@ func TestAllKeys(t *testing.T) {
{"key": 1},
{"key": 2},
{"key": 3},
- {"key": 4}},
+ {"key": 4},
+ },
},
},
"title_dotenv": "DotEnv Example",
@@ -699,8 +873,9 @@ func TestAllKeysWithEnv(t *testing.T) {
v.BindEnv("id")
v.BindEnv("foo.bar")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
- os.Setenv("ID", "13")
- os.Setenv("FOO_BAR", "baz")
+
+ testutil.Setenv(t, "ID", "13")
+ testutil.Setenv(t, "FOO_BAR", "baz")
expectedKeys := sort.StringSlice{"id", "foo.bar"}
expectedKeys.Sort()
@@ -810,13 +985,13 @@ func TestBindPFlags(t *testing.T) {
v := New() // create independent Viper object
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
- var testValues = map[string]*string{
+ testValues := map[string]*string{
"host": nil,
"port": nil,
"endpoint": nil,
}
- var mutatedTestValues = map[string]string{
+ mutatedTestValues := map[string]string{
"host": "localhost",
"port": "6060",
"endpoint": "/public",
@@ -841,12 +1016,13 @@ func TestBindPFlags(t *testing.T) {
}
}
+//nolint:dupl
func TestBindPFlagsStringSlice(t *testing.T) {
tests := []struct {
Expected []string
Value string
}{
- {nil, ""},
+ {[]string{}, ""},
{[]string{"jeden"}, "jeden"},
{[]string{"dwa", "trzy"}, "dwa,trzy"},
{[]string{"cztery", "piec , szesc"}, "cztery,\"piec , szesc\""},
@@ -880,6 +1056,7 @@ func TestBindPFlagsStringSlice(t *testing.T) {
}
if changed {
assert.Equal(t, testValue.Expected, val.StringSlice)
+ assert.Equal(t, testValue.Expected, v.Get("stringslice"))
} else {
assert.Equal(t, defaultVal, val.StringSlice)
}
@@ -887,12 +1064,83 @@ func TestBindPFlagsStringSlice(t *testing.T) {
}
}
+//nolint:dupl
+func TestBindPFlagsStringArray(t *testing.T) {
+ tests := []struct {
+ Expected []string
+ Value string
+ }{
+ {[]string{}, ""},
+ {[]string{"jeden"}, "jeden"},
+ {[]string{"dwa,trzy"}, "dwa,trzy"},
+ {[]string{"cztery,\"piec , szesc\""}, "cztery,\"piec , szesc\""},
+ }
+
+ v := New() // create independent Viper object
+ defaultVal := []string{"default"}
+ v.SetDefault("stringarray", defaultVal)
+
+ for _, testValue := range tests {
+ flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
+ flagSet.StringArray("stringarray", testValue.Expected, "test")
+
+ for _, changed := range []bool{true, false} {
+ flagSet.VisitAll(func(f *pflag.Flag) {
+ f.Value.Set(testValue.Value)
+ f.Changed = changed
+ })
+
+ err := v.BindPFlags(flagSet)
+ if err != nil {
+ t.Fatalf("error binding flag set, %v", err)
+ }
+
+ type TestStr struct {
+ StringArray []string
+ }
+ val := &TestStr{}
+ if err := v.Unmarshal(val); err != nil {
+ t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
+ }
+ if changed {
+ assert.Equal(t, testValue.Expected, val.StringArray)
+ assert.Equal(t, testValue.Expected, v.Get("stringarray"))
+ } else {
+ assert.Equal(t, defaultVal, val.StringArray)
+ }
+ }
+ }
+}
+
+func TestSliceFlagsReturnCorrectType(t *testing.T) {
+ flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
+ flagSet.IntSlice("int", []int{1, 2}, "")
+ flagSet.StringSlice("str", []string{"3", "4"}, "")
+ flagSet.DurationSlice("duration", []time.Duration{5 * time.Second}, "")
+
+ v := New()
+ v.BindPFlags(flagSet)
+
+ all := v.AllSettings()
+
+ if _, ok := all["int"].([]int); !ok {
+ t.Errorf("unexpected type %T expected []int", all["int"])
+ }
+ if _, ok := all["str"].([]string); !ok {
+ t.Errorf("unexpected type %T expected []string", all["str"])
+ }
+ if _, ok := all["duration"].([]time.Duration); !ok {
+ t.Errorf("unexpected type %T expected []time.Duration", all["duration"])
+ }
+}
+
+//nolint:dupl
func TestBindPFlagsIntSlice(t *testing.T) {
tests := []struct {
Expected []int
Value string
}{
- {nil, ""},
+ {[]int{}, ""},
{[]int{1}, "1"},
{[]int{2, 3}, "2,3"},
}
@@ -925,6 +1173,7 @@ func TestBindPFlagsIntSlice(t *testing.T) {
}
if changed {
assert.Equal(t, testValue.Expected, val.IntSlice)
+ assert.Equal(t, testValue.Expected, v.Get("intslice"))
} else {
assert.Equal(t, defaultVal, val.IntSlice)
}
@@ -933,8 +1182,8 @@ func TestBindPFlagsIntSlice(t *testing.T) {
}
func TestBindPFlag(t *testing.T) {
- var testString = "testing"
- var testValue = newStringValue(testString, &testString)
+ testString := "testing"
+ testValue := newStringValue(testString, &testString)
flag := &pflag.Flag{
Name: "testflag",
@@ -952,16 +1201,69 @@ func TestBindPFlag(t *testing.T) {
assert.Equal(t, "testing_mutate", Get("testvalue"))
}
+func TestBindPFlagDetectNilFlag(t *testing.T) {
+ result := BindPFlag("testvalue", nil)
+ assert.Error(t, result)
+}
+
+func TestBindPFlagStringToString(t *testing.T) {
+ tests := []struct {
+ Expected map[string]string
+ Value string
+ }{
+ {map[string]string{}, ""},
+ {map[string]string{"yo": "hi"}, "yo=hi"},
+ {map[string]string{"yo": "hi", "oh": "hi=there"}, "yo=hi,oh=hi=there"},
+ {map[string]string{"yo": ""}, "yo="},
+ {map[string]string{"yo": "", "oh": "hi=there"}, "yo=,oh=hi=there"},
+ }
+
+ v := New() // create independent Viper object
+ defaultVal := map[string]string{}
+ v.SetDefault("stringtostring", defaultVal)
+
+ for _, testValue := range tests {
+ flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
+ flagSet.StringToString("stringtostring", testValue.Expected, "test")
+
+ for _, changed := range []bool{true, false} {
+ flagSet.VisitAll(func(f *pflag.Flag) {
+ f.Value.Set(testValue.Value)
+ f.Changed = changed
+ })
+
+ err := v.BindPFlags(flagSet)
+ if err != nil {
+ t.Fatalf("error binding flag set, %v", err)
+ }
+
+ type TestMap struct {
+ StringToString map[string]string
+ }
+ val := &TestMap{}
+ if err := v.Unmarshal(val); err != nil {
+ t.Fatalf("%+#v cannot unmarshal: %s", testValue.Value, err)
+ }
+ if changed {
+ assert.Equal(t, testValue.Expected, val.StringToString)
+ } else {
+ assert.Equal(t, defaultVal, val.StringToString)
+ }
+ }
+ }
+}
+
func TestBoundCaseSensitivity(t *testing.T) {
assert.Equal(t, "brown", Get("eyes"))
BindEnv("eYEs", "TURTLE_EYES")
- os.Setenv("TURTLE_EYES", "blue")
+
+ testutil.Setenv(t, "TURTLE_EYES", "blue")
assert.Equal(t, "blue", Get("eyes"))
- var testString = "green"
- var testValue = newStringValue(testString, &testString)
+ testString := "green"
+ testValue := newStringValue(testString, &testString)
flag := &pflag.Flag{
Name: "eyeballs",
@@ -1040,9 +1342,11 @@ func TestFindsNestedKeys(t *testing.T) {
},
map[string]interface{}{
"type": "Chocolate",
- }, map[string]interface{}{
+ },
+ map[string]interface{}{
"type": "Blueberry",
- }, map[string]interface{}{
+ },
+ map[string]interface{}{
"type": "Devil's Food",
},
},
@@ -1104,7 +1408,9 @@ func TestReadBufConfig(t *testing.T) {
t.Log(v.AllKeys())
assert.True(t, v.InConfig("name"))
+ assert.True(t, v.InConfig("clothing.jacket"))
assert.False(t, v.InConfig("state"))
+ assert.False(t, v.InConfig("clothing.hat"))
assert.Equal(t, "steve", v.Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing"))
@@ -1135,8 +1441,9 @@ func TestIsSet(t *testing.T) {
v.BindEnv("foo")
v.BindEnv("clothing.hat")
v.BindEnv("clothing.hats")
- os.Setenv("FOO", "bar")
- os.Setenv("CLOTHING_HAT", "bowler")
+
+ testutil.Setenv(t, "FOO", "bar")
+ testutil.Setenv(t, "CLOTHING_HAT", "bowler")
assert.True(t, v.IsSet("eyes")) // in the config file
assert.True(t, v.IsSet("foo")) // in the environment
@@ -1233,6 +1540,14 @@ func TestSub(t *testing.T) {
subv = v.Sub("missing.key")
assert.Equal(t, (*Viper)(nil), subv)
+
+ subv = v.Sub("clothing")
+ assert.Equal(t, subv.parents[0], "clothing")
+
+ subv = v.Sub("clothing").Sub("pants")
+ assert.Equal(t, len(subv.parents), 2)
+ assert.Equal(t, subv.parents[0], "clothing")
+ assert.Equal(t, subv.parents[1], "pants")
}
var hclWriteExpected = []byte(`"foos" = {
@@ -1261,26 +1576,6 @@ var hclWriteExpected = []byte(`"foos" = {
"type" = "donut"`)
-func TestWriteConfigHCL(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("hcl")
- err := v.ReadConfig(bytes.NewBuffer(hclExample))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs("c.hcl"); err != nil {
- t.Fatal(err)
- }
- read, err := afero.ReadFile(fs, "c.hcl")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, hclWriteExpected, read)
-}
-
var jsonWriteExpected = []byte(`{
"batters": {
"batter": [
@@ -1304,26 +1599,6 @@ var jsonWriteExpected = []byte(`{
"type": "donut"
}`)
-func TestWriteConfigJson(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("json")
- err := v.ReadConfig(bytes.NewBuffer(jsonExample))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs("c.json"); err != nil {
- t.Fatal(err)
- }
- read, err := afero.ReadFile(fs, "c.json")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, jsonWriteExpected, read)
-}
-
var propertiesWriteExpected = []byte(`p_id = 0001
p_type = donut
p_name = Cake
@@ -1331,129 +1606,253 @@ p_ppu = 0.55
p_batters.batter.type = Regular
`)
-func TestWriteConfigProperties(t *testing.T) {
- v := New()
+// var yamlWriteExpected = []byte(`age: 35
+// beard: true
+// clothing:
+// jacket: leather
+// pants:
+// size: large
+// trousers: denim
+// eyes: brown
+// hacker: true
+// hobbies:
+// - skateboarding
+// - snowboarding
+// - go
+// name: steve
+// `)
+
+func TestWriteConfig(t *testing.T) {
fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("properties")
- err := v.ReadConfig(bytes.NewBuffer(propertiesExample))
- if err != nil {
- t.Fatal(err)
+ testCases := map[string]struct {
+ configName string
+ inConfigType string
+ outConfigType string
+ fileName string
+ input []byte
+ expectedContent []byte
+ }{
+ "hcl with file extension": {
+ configName: "c",
+ inConfigType: "hcl",
+ outConfigType: "hcl",
+ fileName: "c.hcl",
+ input: hclExample,
+ expectedContent: hclWriteExpected,
+ },
+ "hcl without file extension": {
+ configName: "c",
+ inConfigType: "hcl",
+ outConfigType: "hcl",
+ fileName: "c",
+ input: hclExample,
+ expectedContent: hclWriteExpected,
+ },
+ "hcl with file extension and mismatch type": {
+ configName: "c",
+ inConfigType: "hcl",
+ outConfigType: "json",
+ fileName: "c.hcl",
+ input: hclExample,
+ expectedContent: hclWriteExpected,
+ },
+ "json with file extension": {
+ configName: "c",
+ inConfigType: "json",
+ outConfigType: "json",
+ fileName: "c.json",
+ input: jsonExample,
+ expectedContent: jsonWriteExpected,
+ },
+ "json without file extension": {
+ configName: "c",
+ inConfigType: "json",
+ outConfigType: "json",
+ fileName: "c",
+ input: jsonExample,
+ expectedContent: jsonWriteExpected,
+ },
+ "json with file extension and mismatch type": {
+ configName: "c",
+ inConfigType: "json",
+ outConfigType: "hcl",
+ fileName: "c.json",
+ input: jsonExample,
+ expectedContent: jsonWriteExpected,
+ },
+ "properties with file extension": {
+ configName: "c",
+ inConfigType: "properties",
+ outConfigType: "properties",
+ fileName: "c.properties",
+ input: propertiesExample,
+ expectedContent: propertiesWriteExpected,
+ },
+ "properties without file extension": {
+ configName: "c",
+ inConfigType: "properties",
+ outConfigType: "properties",
+ fileName: "c",
+ input: propertiesExample,
+ expectedContent: propertiesWriteExpected,
+ },
+ "yaml with file extension": {
+ configName: "c",
+ inConfigType: "yaml",
+ outConfigType: "yaml",
+ fileName: "c.yaml",
+ input: yamlExample,
+ expectedContent: yamlWriteExpected,
+ },
+ "yaml without file extension": {
+ configName: "c",
+ inConfigType: "yaml",
+ outConfigType: "yaml",
+ fileName: "c",
+ input: yamlExample,
+ expectedContent: yamlWriteExpected,
+ },
+ "yaml with file extension and mismatch type": {
+ configName: "c",
+ inConfigType: "yaml",
+ outConfigType: "json",
+ fileName: "c.yaml",
+ input: yamlExample,
+ expectedContent: yamlWriteExpected,
+ },
}
- if err := v.WriteConfigAs("c.properties"); err != nil {
- t.Fatal(err)
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ v := New()
+ v.SetFs(fs)
+ v.SetConfigName(tc.fileName)
+ v.SetConfigType(tc.inConfigType)
+
+ err := v.ReadConfig(bytes.NewBuffer(tc.input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ v.SetConfigType(tc.outConfigType)
+ if err := v.WriteConfigAs(tc.fileName); err != nil {
+ t.Fatal(err)
+ }
+ read, err := afero.ReadFile(fs, tc.fileName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, tc.expectedContent, read)
+ })
}
- read, err := afero.ReadFile(fs, "c.properties")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, propertiesWriteExpected, read)
}
func TestWriteConfigTOML(t *testing.T) {
fs := afero.NewMemMapFs()
- v := New()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("toml")
- err := v.ReadConfig(bytes.NewBuffer(tomlExample))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs("c.toml"); err != nil {
- t.Fatal(err)
- }
- // The TOML String method does not order the contents.
- // Therefore, we must read the generated file and compare the data.
- v2 := New()
- v2.SetFs(fs)
- v2.SetConfigName("c")
- v2.SetConfigType("toml")
- v2.SetConfigFile("c.toml")
- err = v2.ReadInConfig()
- if err != nil {
- t.Fatal(err)
+ testCases := map[string]struct {
+ configName string
+ configType string
+ fileName string
+ input []byte
+ }{
+ "with file extension": {
+ configName: "c",
+ configType: "toml",
+ fileName: "c.toml",
+ input: tomlExample,
+ },
+ "without file extension": {
+ configName: "c",
+ configType: "toml",
+ fileName: "c",
+ input: tomlExample,
+ },
}
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ v := New()
+ v.SetFs(fs)
+ v.SetConfigName(tc.configName)
+ v.SetConfigType(tc.configType)
+ err := v.ReadConfig(bytes.NewBuffer(tc.input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := v.WriteConfigAs(tc.fileName); err != nil {
+ t.Fatal(err)
+ }
- assert.Equal(t, v.GetString("title"), v2.GetString("title"))
- assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
- assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
- assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
+ // The TOML String method does not order the contents.
+ // Therefore, we must read the generated file and compare the data.
+ v2 := New()
+ v2.SetFs(fs)
+ v2.SetConfigName(tc.configName)
+ v2.SetConfigType(tc.configType)
+ v2.SetConfigFile(tc.fileName)
+ err = v2.ReadInConfig()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assert.Equal(t, v.GetString("title"), v2.GetString("title"))
+ assert.Equal(t, v.GetString("owner.bio"), v2.GetString("owner.bio"))
+ assert.Equal(t, v.GetString("owner.dob"), v2.GetString("owner.dob"))
+ assert.Equal(t, v.GetString("owner.organization"), v2.GetString("owner.organization"))
+ })
+ }
}
-var dotenvWriteExpected = []byte(`
-TITLE="DotEnv Write Example"
-NAME=Oreo
-KIND=Biscuit
-`)
-
func TestWriteConfigDotEnv(t *testing.T) {
fs := afero.NewMemMapFs()
- v := New()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("env")
- err := v.ReadConfig(bytes.NewBuffer(dotenvWriteExpected))
- if err != nil {
- t.Fatal(err)
- }
- if err := v.WriteConfigAs("c.env"); err != nil {
- t.Fatal(err)
+ testCases := map[string]struct {
+ configName string
+ configType string
+ fileName string
+ input []byte
+ }{
+ "with file extension": {
+ configName: "c",
+ configType: "env",
+ fileName: "c.env",
+ input: dotenvExample,
+ },
+ "without file extension": {
+ configName: "c",
+ configType: "env",
+ fileName: "c",
+ input: dotenvExample,
+ },
}
+ for name, tc := range testCases {
+ t.Run(name, func(t *testing.T) {
+ v := New()
+ v.SetFs(fs)
+ v.SetConfigName(tc.configName)
+ v.SetConfigType(tc.configType)
+ err := v.ReadConfig(bytes.NewBuffer(tc.input))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := v.WriteConfigAs(tc.fileName); err != nil {
+ t.Fatal(err)
+ }
- // The TOML String method does not order the contents.
- // Therefore, we must read the generated file and compare the data.
- v2 := New()
- v2.SetFs(fs)
- v2.SetConfigName("c")
- v2.SetConfigType("env")
- v2.SetConfigFile("c.env")
- err = v2.ReadInConfig()
- if err != nil {
- t.Fatal(err)
- }
+ // The TOML String method does not order the contents.
+ // Therefore, we must read the generated file and compare the data.
+ v2 := New()
+ v2.SetFs(fs)
+ v2.SetConfigName(tc.configName)
+ v2.SetConfigType(tc.configType)
+ v2.SetConfigFile(tc.fileName)
+ err = v2.ReadInConfig()
+ if err != nil {
+ t.Fatal(err)
+ }
- assert.Equal(t, v.GetString("title"), v2.GetString("title"))
- assert.Equal(t, v.GetString("type"), v2.GetString("type"))
- assert.Equal(t, v.GetString("kind"), v2.GetString("kind"))
-}
-
-var yamlWriteExpected = []byte(`age: 35
-beard: true
-clothing:
- jacket: leather
- pants:
- size: large
- trousers: denim
-eyes: brown
-hacker: true
-hobbies:
-- skateboarding
-- snowboarding
-- go
-name: steve
-`)
-
-func TestWriteConfigYAML(t *testing.T) {
- v := New()
- fs := afero.NewMemMapFs()
- v.SetFs(fs)
- v.SetConfigName("c")
- v.SetConfigType("yaml")
- err := v.ReadConfig(bytes.NewBuffer(yamlExample))
- if err != nil {
- t.Fatal(err)
+ assert.Equal(t, v.GetString("title_dotenv"), v2.GetString("title_dotenv"))
+ assert.Equal(t, v.GetString("type_dotenv"), v2.GetString("type_dotenv"))
+ assert.Equal(t, v.GetString("kind_dotenv"), v2.GetString("kind_dotenv"))
+ })
}
- if err := v.WriteConfigAs("c.yaml"); err != nil {
- t.Fatal(err)
- }
- read, err := afero.ReadFile(fs, "c.yaml")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, yamlWriteExpected, read)
}
func TestSafeWriteConfig(t *testing.T) {
@@ -1465,7 +1864,7 @@ func TestSafeWriteConfig(t *testing.T) {
v.SetConfigType("yaml")
require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)))
require.NoError(t, v.SafeWriteConfig())
- read, err := afero.ReadFile(fs, "/test/c.yaml")
+ read, err := afero.ReadFile(fs, testutil.AbsFilePath(t, "/test/c.yaml"))
require.NoError(t, err)
assert.Equal(t, yamlWriteExpected, read)
}
@@ -1482,7 +1881,7 @@ func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
func TestSafeWriteConfigWithExistingFile(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
- fs.Create("/test/c.yaml")
+ fs.Create(testutil.AbsFilePath(t, "/test/c.yaml"))
v.SetFs(fs)
v.AddConfigPath("/test")
v.SetConfigName("c")
@@ -1518,11 +1917,29 @@ func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
}
+func TestWriteHiddenFile(t *testing.T) {
+ v := New()
+ fs := afero.NewMemMapFs()
+ fs.Create(testutil.AbsFilePath(t, "/test/.config"))
+ v.SetFs(fs)
+
+ v.SetConfigName(".config")
+ v.SetConfigType("yaml")
+ v.AddConfigPath("/test")
+
+ err := v.ReadInConfig()
+ require.NoError(t, err)
+
+ err = v.WriteConfig()
+ require.NoError(t, err)
+}
+
var yamlMergeExampleTgt = []byte(`
hello:
pop: 37890
largenum: 765432101234567
num2pow63: 9223372036854775808
+ universe: null
world:
- us
- uk
@@ -1543,6 +1960,24 @@ hello:
fu: bar
`)
+var jsonMergeExampleTgt = []byte(`
+{
+ "hello": {
+ "foo": null,
+ "pop": 123456
+ }
+}
+`)
+
+var jsonMergeExampleSrc = []byte(`
+{
+ "hello": {
+ "foo": "foo str",
+ "pop": "pop str"
+ }
+}
+`)
+
func TestMergeConfig(t *testing.T) {
v := New()
v.SetConfigType("yml")
@@ -1566,6 +2001,10 @@ func TestMergeConfig(t *testing.T) {
t.Fatalf("uint pop != 37890, = %d", pop)
}
+ if pop := v.GetUint16("hello.pop"); pop != uint16(37890) {
+ t.Fatalf("uint pop != 37890, = %d", pop)
+ }
+
if pop := v.GetUint32("hello.pop"); pop != 37890 {
t.Fatalf("uint32 pop != 37890, = %d", pop)
}
@@ -1615,6 +2054,26 @@ func TestMergeConfig(t *testing.T) {
}
}
+func TestMergeConfigOverrideType(t *testing.T) {
+ v := New()
+ v.SetConfigType("json")
+ if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil {
+ t.Fatal(err)
+ }
+
+ if pop := v.GetString("hello.pop"); pop != "pop str" {
+ t.Fatalf("pop != \"pop str\", = %s", pop)
+ }
+
+ if foo := v.GetString("hello.foo"); foo != "foo str" {
+ t.Fatalf("foo != \"foo str\", = %s", foo)
+ }
+}
+
func TestMergeConfigNoMerge(t *testing.T) {
v := New()
v.SetConfigType("yml")
@@ -1820,18 +2279,18 @@ func TestCaseInsensitiveSet(t *testing.T) {
Reset()
m1 := map[string]interface{}{
"Foo": 32,
- "Bar": map[interface{}]interface {
- }{
+ "Bar": map[interface{}]interface{}{
"ABc": "A",
- "cDE": "B"},
+ "cDE": "B",
+ },
}
m2 := map[string]interface{}{
"Foo": 52,
- "Bar": map[interface{}]interface {
- }{
+ "Bar": map[interface{}]interface{}{
"bCd": "A",
- "eFG": "B"},
+ "eFG": "B",
+ },
}
Set("Given1", m1)
@@ -1921,7 +2380,7 @@ func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
watchDir, err := ioutil.TempDir("", "")
require.Nil(t, err)
configFile := path.Join(watchDir, "config.yaml")
- err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640)
+ err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0o640)
require.Nil(t, err)
cleanup := func() {
os.RemoveAll(watchDir)
@@ -1938,11 +2397,11 @@ func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func
watchDir, err := ioutil.TempDir("", "")
require.Nil(t, err)
dataDir1 := path.Join(watchDir, "data1")
- err = os.Mkdir(dataDir1, 0777)
+ err = os.Mkdir(dataDir1, 0o777)
require.Nil(t, err)
realConfigFile := path.Join(dataDir1, "config.yaml")
t.Logf("Real config file location: %s\n", realConfigFile)
- err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640)
+ err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640)
require.Nil(t, err)
cleanup := func() {
os.RemoveAll(watchDir)
@@ -1977,13 +2436,16 @@ func TestWatchFile(t *testing.T) {
t.Logf("test config file: %s\n", configFile)
wg := sync.WaitGroup{}
wg.Add(1)
+ var wgDoneOnce sync.Once // OnConfigChange is called twice on Windows
v.OnConfigChange(func(in fsnotify.Event) {
t.Logf("config file changed")
- wg.Done()
+ wgDoneOnce.Do(func() {
+ wg.Done()
+ })
})
v.WatchConfig()
// when overwriting the file and waiting for the custom change notification handler to be triggered
- err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640)
+ err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0o640)
wg.Wait()
// then the config value should have changed
require.Nil(t, err)
@@ -2006,10 +2468,10 @@ func TestWatchFile(t *testing.T) {
wg.Add(1)
// when link to another `config.yaml` file
dataDir2 := path.Join(watchDir, "data2")
- err := os.Mkdir(dataDir2, 0777)
+ err := os.Mkdir(dataDir2, 0o777)
require.Nil(t, err)
configFile2 := path.Join(dataDir2, "config.yaml")
- err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640)
+ err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640)
require.Nil(t, err)
// change the symlink using the `ln -sfn` command
err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()
@@ -2038,25 +2500,25 @@ func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
assert.Equal(t, "cobra_flag", config.Foo.Bar)
}
-var yamlExampleWithDot = []byte(`Hacker: true
-name: steve
-hobbies:
- - skateboarding
- - snowboarding
- - go
-clothing:
- jacket: leather
- trousers: denim
- pants:
- size: large
-age: 35
-eyes : brown
-beard: true
-emails:
- steve@hacker.com:
- created: 01/02/03
- active: true
-`)
+// var yamlExampleWithDot = []byte(`Hacker: true
+// name: steve
+// hobbies:
+// - skateboarding
+// - snowboarding
+// - go
+// clothing:
+// jacket: leather
+// trousers: denim
+// pants:
+// size: large
+// age: 35
+// eyes : brown
+// beard: true
+// emails:
+// steve@hacker.com:
+// created: 01/02/03
+// active: true
+// `)
func TestKeyDelimiter(t *testing.T) {
v := NewWithOptions(KeyDelimiter("::"))
@@ -2105,6 +2567,56 @@ func TestKeyDelimiter(t *testing.T) {
assert.Equal(t, expected, actual)
}
+var yamlDeepNestedSlices = []byte(`TV:
+- title: "The Expanse"
+ title_i18n:
+ USA: "The Expanse"
+ Japan: "γ¨γ―γΉγγ³γΉ -ε·¨η£γγγγ-"
+ seasons:
+ - first_released: "December 14, 2015"
+ episodes:
+ - title: "Dulcinea"
+ air_date: "December 14, 2015"
+ - title: "The Big Empty"
+ air_date: "December 15, 2015"
+ - title: "Remember the Cant"
+ air_date: "December 22, 2015"
+ - first_released: "February 1, 2017"
+ episodes:
+ - title: "Safe"
+ air_date: "February 1, 2017"
+ - title: "Doors & Corners"
+ air_date: "February 1, 2017"
+ - title: "Static"
+ air_date: "February 8, 2017"
+ episodes:
+ - ["Dulcinea", "The Big Empty", "Remember the Cant"]
+ - ["Safe", "Doors & Corners", "Static"]
+`)
+
+func TestSliceIndexAccess(t *testing.T) {
+ v.SetConfigType("yaml")
+ r := strings.NewReader(string(yamlDeepNestedSlices))
+
+ err := v.unmarshalReader(r, v.config)
+ require.NoError(t, err)
+
+ assert.Equal(t, "The Expanse", v.GetString("tv.0.title"))
+ assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released"))
+ assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title"))
+ assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date"))
+
+ // Test nested keys with capital letters
+ assert.Equal(t, "The Expanse", v.GetString("tv.0.title_i18n.USA"))
+ assert.Equal(t, "γ¨γ―γΉγγ³γΉ -ε·¨η£γγγγ-", v.GetString("tv.0.title_i18n.Japan"))
+
+ // Test for index out of bounds
+ assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released"))
+
+ // Accessing multidimensional arrays
+ assert.Equal(t, "Static", v.GetString("tv.0.episodes.1.2"))
+}
+
func BenchmarkGetBool(b *testing.B) {
key := "BenchmarkGetBool"
v = New()
@@ -2141,3 +2653,10 @@ func BenchmarkGetBoolFromMap(b *testing.B) {
}
}
}
+
+// Skip some tests on Windows that kept failing when Windows was added to the CI as a target.
+func skipWindows(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("Skip test on Windows")
+ }
+}
diff --git a/viper_yaml_test.go b/viper_yaml_test.go
new file mode 100644
index 0000000..264446b
--- /dev/null
+++ b/viper_yaml_test.go
@@ -0,0 +1,53 @@
+package viper
+
+var yamlExample = []byte(`Hacker: true
+name: steve
+hobbies:
+ - skateboarding
+ - snowboarding
+ - go
+clothing:
+ jacket: leather
+ trousers: denim
+ pants:
+ size: large
+age: 35
+eyes : brown
+beard: true
+`)
+
+var yamlWriteExpected = []byte(`age: 35
+beard: true
+clothing:
+ jacket: leather
+ pants:
+ size: large
+ trousers: denim
+eyes: brown
+hacker: true
+hobbies:
+ - skateboarding
+ - snowboarding
+ - go
+name: steve
+`)
+
+var yamlExampleWithDot = []byte(`Hacker: true
+name: steve
+hobbies:
+ - skateboarding
+ - snowboarding
+ - go
+clothing:
+ jacket: leather
+ trousers: denim
+ pants:
+ size: large
+age: 35
+eyes : brown
+beard: true
+emails:
+ steve@hacker.com:
+ created: 01/02/03
+ active: true
+`)
diff --git a/watch.go b/watch.go
new file mode 100644
index 0000000..1ce84ea
--- /dev/null
+++ b/watch.go
@@ -0,0 +1,12 @@
+//go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows
+// +build darwin dragonfly freebsd openbsd linux netbsd solaris windows
+
+package viper
+
+import "github.com/fsnotify/fsnotify"
+
+type watcher = fsnotify.Watcher
+
+func newWatcher() (*watcher, error) {
+ return fsnotify.NewWatcher()
+}
diff --git a/watch_unsupported.go b/watch_unsupported.go
new file mode 100644
index 0000000..7e27153
--- /dev/null
+++ b/watch_unsupported.go
@@ -0,0 +1,32 @@
+//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
+// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
+
+package viper
+
+import (
+ "fmt"
+ "runtime"
+
+ "github.com/fsnotify/fsnotify"
+)
+
+func newWatcher() (*watcher, error) {
+ return &watcher{}, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
+}
+
+type watcher struct {
+ Events chan fsnotify.Event
+ Errors chan error
+}
+
+func (*watcher) Close() error {
+ return nil
+}
+
+func (*watcher) Add(name string) error {
+ return nil
+}
+
+func (*watcher) Remove(name string) error {
+ return nil
+}