Showing preview only (289K chars total). Download the full file or copy to clipboard to get everything.
Repository: spf13/viper
Branch: master
Commit: 528f7416c4b5
Files: 63
Total size: 272.1 KB
Directory structure:
gitextract_5z5ozi0n/
├── .editorconfig
├── .envrc
├── .github/
│ ├── .editorconfig
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yaml
│ │ ├── config.yml
│ │ └── feature_request.yaml
│ ├── PULL_REQUEST_TEMPLATES.md
│ ├── dependabot.yaml
│ ├── octoslash/
│ │ ├── policies/
│ │ │ ├── collaborator.cedar
│ │ │ └── triager.cedar
│ │ └── principals.json
│ ├── release.yml
│ └── workflows/
│ ├── checks.yaml
│ ├── ci.yaml
│ ├── octoslash.yaml
│ └── stale.yaml
├── .gitignore
├── .golangci.yaml
├── .yamlignore
├── .yamllint.yaml
├── LICENSE
├── Makefile
├── README.md
├── TROUBLESHOOTING.md
├── UPGRADE.md
├── encoding.go
├── encoding_test.go
├── errors.go
├── experimental.go
├── file.go
├── finder.go
├── finder_example_test.go
├── finder_test.go
├── flags.go
├── flags_test.go
├── flake.nix
├── go.mod
├── go.sum
├── internal/
│ ├── encoding/
│ │ ├── dotenv/
│ │ │ ├── codec.go
│ │ │ ├── codec_test.go
│ │ │ └── map_utils.go
│ │ ├── json/
│ │ │ ├── codec.go
│ │ │ └── codec_test.go
│ │ ├── toml/
│ │ │ ├── codec.go
│ │ │ └── codec_test.go
│ │ └── yaml/
│ │ ├── codec.go
│ │ └── codec_test.go
│ ├── features/
│ │ ├── bind_struct.go
│ │ ├── bind_struct_default.go
│ │ ├── finder.go
│ │ └── finder_default.go
│ └── testutil/
│ └── filepath.go
├── logger.go
├── overrides_test.go
├── remote/
│ ├── go.mod
│ ├── go.sum
│ └── remote.go
├── remote.go
├── util.go
├── util_test.go
├── viper.go
├── viper_test.go
└── viper_yaml_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
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
[*.nix]
indent_size = 2
[.golangci.yaml]
indent_size = 2
================================================
FILE: .envrc
================================================
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
fi
use flake . --impure
================================================
FILE: .github/.editorconfig
================================================
[{*.yml,*.yaml}]
indent_size = 2
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
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!
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
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
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
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.
================================================
FILE: .github/PULL_REQUEST_TEMPLATES.md
================================================
<!--
Thank you for sending a pull request! Here some tips for contributors:
1. Fill the description template below.
2. Include appropriate tests (if necessary). Make sure that all CI checks passed.
3. If the Pull Request is a work in progress, make use of GitHub's "Draft PR" feature and mark it as such.
-->
**Overview**:
<!-- Describe your changes briefly here. -->
**What problem does it solve?**:
<!--
- Please state in detail why we need this PR and what it solves.
- If your PR closes some of the existing issues, please add links to them here.
Mentioned issues will be automatically closed.
Usage: "Closes #<issue number>", or "Closes (paste link of issue)"
-->
**Special notes for a reviewer**:
================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
- package-ecosystem: gomod
directory: /
labels:
- area/dependencies
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
labels:
- area/dependencies
schedule:
interval: daily
================================================
FILE: .github/octoslash/policies/collaborator.cedar
================================================
permit(
principal in Role::"Collaborator",
action,
resource
);
================================================
FILE: .github/octoslash/policies/triager.cedar
================================================
permit(
principal in Role::"Triager",
action in [Action::"close", Action::"add-label", Action::"remove-label", Action::"self-assign", Action::"self-unassign"],
resource
);
================================================
FILE: .github/octoslash/principals.json
================================================
[
{
"uid": { "type": "User", "id": "1226384" },
"attrs": { "login": "sagikazarmark" },
"parents": [{ "type": "Role", "id": "Collaborator" }]
},
{
"uid": { "type": "User", "id": "805695" },
"attrs": { "login": "spacez320" },
"parents": [{ "type": "Role", "id": "Triager" }]
}
]
================================================
FILE: .github/release.yml
================================================
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:
- "*"
================================================
FILE: .github/workflows/checks.yaml
================================================
name: PR Checks
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
permissions:
pull-requests: read
jobs:
release-label:
name: Release note label
runs-on: ubuntu-latest
steps:
- name: Check minimum labels
uses: mheap/github-action-required-labels@388fd6af37b34cdfe5a23b37060e763217e58b03 # v5.5
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
release-note/misc
================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI
on:
push:
branches: [master]
pull_request:
permissions:
contents: read
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: js
goarch: wasm
- goos: aix
goarch: ppc64
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: "1.25"
- 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.23", "1.24", "1.25"]
tags: ["", "viper_finder", "viper_bind_struct"]
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -race -v -tags '${{ matrix.tags }}' -shuffle=on ./...
if: runner.os != 'Windows'
- name: Test (without race detector)
run: go test -v -tags '${{ matrix.tags }}' -shuffle=on ./...
if: runner.os == 'Windows'
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Go
uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
with:
go-version: "1.25"
- name: Lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.4.0
dev:
name: Developer environment
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Nix
uses: cachix/install-nix-action@3715ab1a11cac9e991980d7b4a28d80c7ebdd8f9 # v27
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
- name: Check
run: nix flake check --impure
- name: Dev shell
run: nix develop --impure
dependency-review:
name: Dependency review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Dependency Review
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
================================================
FILE: .github/workflows/octoslash.yaml
================================================
name: Octoslash
on: issue_comment
permissions:
issues: write
pull-requests: write
jobs:
run:
name: Run
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Octoslash
uses: sagikazarmark/octoslash-action@v0
================================================
FILE: .github/workflows/stale.yaml
================================================
name: "Close stale issues"
on:
schedule:
- cron: "0 3 * * *"
permissions:
actions: write
issues: write
# contents: write # only for delete-branch option
# pull-requests: write
jobs:
stale:
name: Stale
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 30
days-before-close: 5
days-before-pr-stale: -1 # Ignore all PRs
stale-issue-message: "Issues with no activity for 30 days are marked stale and subject to being closed."
close-issue-message: "This issue has been closed for inactivity. Please re-open if this was a mistake."
exempt-issue-labels: "lifecycle/keep"
stale-issue-label: "lifecycle/stale"
operations-per-run: 500 # Must respect GitHub API limits
debug-only: false # Set this to 'true' to disable stale issue management
================================================
FILE: .gitignore
================================================
/.devenv/
/.direnv/
/.idea/
/.pre-commit-config.yaml
/bin/
/build/
/var/
/vendor/
================================================
FILE: .golangci.yaml
================================================
version: "2"
run:
timeout: 5m
linters:
enable:
- bodyclose
- dogsled
- dupl
- durationcheck
- exhaustive
- gocritic
- godot
- gomoddirectives
- goprintffuncname
- govet
- importas
- ineffassign
- makezero
- misspell
- nakedret
- nilerr
- noctx
- nolintlint
- prealloc
- predeclared
- revive
- rowserrcheck
- sqlclosecheck
- staticcheck
- tparallel
- unconvert
- unparam
- unused
- wastedassign
- whitespace
# fixme
# - cyclop
# - errcheck
# - errorlint
# - exhaustivestruct
# - forbidigo
# - forcetypeassert
# - gochecknoglobals
# - gochecknoinits
# - gocognit
# - goconst
# - gocyclo
# - 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
exclusions:
rules:
- linters:
- errcheck
- noctx
path: _test.go
presets:
- std-error-handling
settings:
revive:
rules:
- name: blank-imports
- name: context-as-argument
arguments:
- allowTypesBefore: "*testing.T"
- name: context-keys-type
- name: dot-imports
- name: empty-block
- name: error-naming
- name: error-return
- name: error-strings
- name: errorf
- name: exported
arguments:
- "sayRepetitiveInsteadOfStutters" # make error messages clearer
- name: increment-decrement
- name: indent-error-flow
- name: package-comments
disabled: true # disable package comments rule for now
- name: range
- name: redefines-builtin-id
- name: superfluous-else
- name: time-naming
- name: unexported-return
- name: unreachable-code
- name: unused-parameter
- name: var-declaration
- name: var-naming
# TODO: add more revive rules
misspell:
locale: US
nolintlint:
allow-unused: false # report any unused nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
gocritic:
# Enable multiple checks by tags. See "Tags" section in https://github.com/go-critic/go-critic#usage.
enabled-tags:
- diagnostic
- experimental
- opinionated
- style
disabled-checks:
- importShadow
- unnamedResult
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
# - golines
settings:
gci:
sections:
- standard
- default
- localmodule
================================================
FILE: .yamlignore
================================================
# TODO: FIXME
/.github/
================================================
FILE: .yamllint.yaml
================================================
ignore-from-file: [.gitignore, .yamlignore]
extends: default
rules:
line-length: disable
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014 Steve Francia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# 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
endif
TEST_FORMAT = short-verbose
endif
# Dependency versions
GOTESTSUM_VERSION = 1.9.0
GOLANGCI_VERSION = 1.53.3
# Add the ability to override some variables
# Use with care
-include override.mk
.PHONY: clear
clear: ## Clear the working area and the project
rm -rf bin/
.PHONY: check
check: test lint ## Run tests and linters
TEST_PKGS ?= ./...
.PHONY: test
test: TEST_FORMAT ?= short
test: SHELL = /bin/bash
test: export CGO_ENABLED=1
test: bin/gotestsum ## Run tests
@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},./...)
.PHONY: lint
lint: lint-go lint-yaml
lint: ## Run linters
.PHONY: lint-go
lint-go:
golangci-lint run $(if ${CI},--out-format github-actions,)
.PHONY: lint-yaml
lint-yaml:
yamllint $(if ${CI},-f github,) --no-warnings .
.PHONY: fmt
fmt: ## Format code
golangci-lint run --fix
deps: bin/golangci-lint bin/gotestsum yamllint
deps: ## Install dependencies
bin/gotestsum:
@mkdir -p bin
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum && chmod +x ./bin/gotestsum
bin/golangci-lint:
@mkdir -p bin
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- v${GOLANGCI_VERSION}
.PHONY: yamllint
yamllint:
pip3 install --user yamllint
# 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
.PHONY: help
.DEFAULT_GOAL := help
help:
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
# Variable outputting/exporting rules
var-%: ; @echo $($*)
varexport-%: ; @echo $*=$($*)
================================================
FILE: README.md
================================================
> ## 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!**

[](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://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:
* [Hugo](http://gohugo.io)
* [EMC RexRay](http://rexray.readthedocs.org/en/stable/)
* [Imgur’s Incus](https://github.com/Imgur/incus)
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
* [Docker Notary](https://github.com/docker/Notary)
* [BloomApi](https://www.bloomapi.com/)
* [doctl](https://github.com/digitalocean/doctl)
* [Clairctl](https://github.com/jgsqware/clairctl)
* [Mercure](https://mercure.rocks)
* [Meshery](https://github.com/meshery/meshery)
* [Bearer](https://github.com/bearer/bearer)
* [Coder](https://github.com/coder/coder)
* [Vitess](https://vitess.io/)
## Install
```shell
go get github.com/spf13/viper
```
> **NOTE** Viper uses [Go Modules](https://go.dev/wiki/Modules) to manage dependencies.
## Why use Viper?
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 any application, and can handle all types of configuration needs
and formats. It supports:
* setting defaults
* setting explicit values
* reading config files
* dynamic discovery of config files across multiple locations
* reading from environment variables
* reading from remote systems (e.g. Etcd or Consul)
* reading from command line flags
* reading from buffers
* live watching and updating configuration
* aliasing configuration keys for easy refactoring
Viper can be thought of as a registry for all of your applications'
configuration needs.
## Putting Values in Viper
Viper can read from multiple configuration sources and merges them together
into one set of configuration keys and values.
Viper uses the following precedence for merging:
* explicit call to `Set`
* flags
* environment variables
* config files
* external key/value stores
* defaults
> **NOTE** Viper configuration keys are case insensitive.
### Reading Config Files
Viper requires minimal configuration to load config files. Viper currently supports:
* JSON
* TOML
* YAML
* INI
* envfile
* Java Propeties
A single Viper instance only supports a single configuration file, but multiple
paths may be searched for one.
Here is an example of how to use Viper to search for and read a configuration
file. At least one path should be provided where a configuration file is
expected.
```go
// Name of the config file without an extension (Viper will intuit the type
// from an extension on the actual file)
viper.SetConfigName("config")
// Add search paths to find the file
viper.AddConfigPath("/etc/appname/")
viper.AddConfigPath("$HOME/.appname")
viper.AddConfigPath(".")
// Find and read the config file
err := viper.ReadInConfig()
// Handle errors
if err != nil {
panic(fmt.Errorf("fatal error config file: %w", err))
}
```
You can handle the specific case where no config file is found.
```go
var fileLookupError viper.FileLookupError
if err := viper.ReadInConfig(); err != nil {
if errors.As(err, &fileLookupError) {
// Indicates an explicitly set config file is not found (such as with
// using `viper.SetConfigFile`) or that no config file was found in
// any search path (such as when using `viper.AddConfigPath`)
} else {
// Config file was found but another error was produced
}
}
// Config file found and successfully parsed
```
> **NOTE (since 1.6)** You can also have a file without an extension and
> specify the format programmatically, which is useful for files that naturally
> have no extension (e.g., `.bashrc`).
### Writing Config Files
At times you may want to store all configuration modifications made during run
time.
```go
// Writes current config to the path set by `AddConfigPath` and `SetConfigName`
viper.WriteConfig()
viper.SafeWriteConfig() // Like the above, but will error if the config file exists
// Writes current config to a specific place
viper.WriteConfigAs("/path/to/my/.config")
// Will error since it has already been written
viper.SafeWriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.other_config")
```
As a rule of the thumb, methods prefixed with `Safe` won't overwrite any
existing file, while other methods will.
### Watching and Re-reading Config Files
Gone are the days of needing to restart a server to have a config take
effect--Viper powered applications can read an update to a config file while
running and not miss a beat.
It's also possible to provide a function for Viper to run each time a change
occurs.
```go
// All config paths must be defined prior to calling `WatchConfig()`
viper.AddConfigPath("$HOME/.appname")
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()
```
### Reading Config from `io.Reader`
Viper predefines many configuration sources but you can also implement your own
required configuration source.
```go
viper.SetConfigType("yaml")
var yamlExample = []byte(`
hacker: true
hobbies:
- skateboarding
- snowboarding
- go
name: steve
`)
viper.ReadConfig(bytes.NewBuffer(yamlExample))
viper.Get("name") // "steve"
```
### Setting Defaults
A good configuration system will support default values, which are used if a
key hasn't been set in some other way.
Examples:
```go
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
```
### Setting Overrides
Viper allows explict setting of configuration, such as from your own
application logic.
```go
viper.Set("verbose", true)
viper.Set("host.port", 5899) // Set an embedded key
```
### Registering and Using Aliases
Aliases permit a single value to be referenced by multiple keys
```go
viper.RegisterAlias("loud", "Verbose")
viper.Set("verbose", true) // Same result as next line
viper.Set("loud", true) // Same result as prior line
viper.GetBool("loud") // true
viper.GetBool("verbose") // true
```
### Working with Environment Variables
Viper has full support for environment variables.
> **NOTE** Unlike other configuration sources, environment variables are case
> sensitive.
```go
// Tells Viper to use this prefix when reading environment variables
viper.SetEnvPrefix("spf")
// Viper will look for "SPF_ID", automatically uppercasing the prefix and key
viper.BindEnv("id")
// Alternatively, we can search for any environment variable prefixed and load
// them in
viper.AutomaticEnv()
os.Setenv("SPF_ID", "13")
id := viper.Get("id") // 13
```
* By default, empty environment variables are considered unset and will fall back to
the next configuration source, unless `AllowEmptyEnv` is used.
* Viper does not "cache" environment variables--the value will be read each
time it is accessed.
* `SetEnvKeyReplacer` and `EnvKeyReplacer` allow you to rewrite environment
variable keys, which is useful to merge SCREAMING_SNAKE_CASE environment
variables with kebab-cased configuration values from other sources.
### Working with Flags
Viper has the ability to bind to flags. Specifically, Viper supports
[pflag](https://github.com/spf13/pflag/) as used in the
[Cobra](https://github.com/spf13/cobra) library.
Like environment variables, the value is not set when the binding method is
called, but when it is accessed.
For individual flags, the `BindPFlag` method provides this functionality.
```go
serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
```
You can also bind an existing set of pflags.
```go
pflag.Int("flagname", 1234, "help message for flagname")
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
i := viper.GetInt("flagname") // Retrieve values from viper instead of pflag
```
The standard library [flag](https://golang.org/pkg/flag/) package is not
directly supported, but may be parsed through pflag.
```go
package main
import (
"flag"
"github.com/spf13/pflag"
)
func main() {
// Using standard library "flag" package
flag.Int("flagname", 1234, "help message for flagname")
// Pass standard library flags to pflag
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
// Viper takes over
viper.BindPFlags(pflag.CommandLine)
}
```
Use of pflag may be avoided entirely by implementing the `FlagValue` and
`FlagValueSet` interfaces.
```go
// Implementing FlagValue
type myFlag struct {}
func (f myFlag) HasChanged() bool { return false }
func (f myFlag) Name() string { return "my-flag-name" }
func (f myFlag) ValueString() string { return "my-flag-value" }
func (f myFlag) ValueType() string { return "string" }
viper.BindFlagValue("my-flag-name", myFlag{})
// Implementing FlagValueSet
type myFlagSet struct {
flags []myFlag
}
func (f myFlagSet) VisitAll(fn func(FlagValue)) {
for _, flag := range flags {
fn(flag)
}
}
fSet := myFlagSet{
flags: []myFlag{myFlag{}, myFlag{}},
}
viper.BindFlagValues("my-flags", fSet)
```
### Remote Key/Value Store Support
To enable remote support in Viper, do a blank import of the `viper/remote`
package.
```go
import _ "github.com/spf13/viper/remote"
```
Viper supports the following remote key/value stores. Examples for each are
provided below.
* Etcd and Etcd3
* Consul
* Firestore
* NATS
Viper will read a config string retrieved from a path in a key/value store.
Viper supports multiple hosts separated by `;`. For example:
`http://127.0.0.1:4001;http://127.0.0.1:4002`.
#### Encryption
Viper uses [crypt](https://github.com/sagikazarmark/crypt) to retrieve
configuration from the key/value 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.
Crypt has a command-line helper that you can use to put configurations in your
key/value store.
```bash
$ go get github.com/sagikazarmark/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
$ crypt get -plaintext /config/hugo.json
```
See the Crypt documentation for examples of how to set encrypted values, or
how to use Consul.
### Remote Key/Value Store Examples (Unencrypted)
#### etcd
```go
viper.AddRemoteProvider("etcd", "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()
```
#### 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
Given a Consul key `MY_CONSUL_KEY` with the value:
```json
{
"port": 8080,
"hostname": "myhostname.com"
}
```
```go
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // Need to explicitly set this to json
err := viper.ReadRemoteConfig()
fmt.Println(viper.Get("port")) // 8080
```
#### 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.
#### NATS
```go
viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config")
viper.SetConfigType("json")
err := viper.ReadRemoteConfig()
```
### Remote Key/Value Store Examples (Encrypted)
```go
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
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()
```
### Watching Key/Value Store Changes
```go
// Alternatively, you can create a new viper instance
var runtime_viper = viper.New()
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
// Read from remote config the first time
err := runtime_viper.ReadRemoteConfig()
// Unmarshal config
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
// 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
runtime_viper.Unmarshal(&runtime_conf)
}
}()
```
## Getting Values From Viper
The simplest way to retrieve configuration values from Viper is to use `Get*`
functions. `Get` will return an any type, but specific types may be retrieved
with `Get<Type>` functions.
Note that each `Get*` function will return a zero value if it’s key is not
found. To check if a key exists, use the `IsSet` method.
Nested keys use `.` as a delimiter and numbers for array indexes. Given the
following configuration:
```jsonc
{
"datastore": {
"metric": {
"host": "127.0.0.1",
"ports": [
5799,
6029
]
}
}
}
```
```go
GetString("datastore.metric.host") // "127.0.0.1"
GetInt("host.ports.1") // 6029
```
> **NOTE** Viper _does not_ deep merge configuration values. Complex values
> that are overridden will be entirely replaced.
If there exists a key that matches the delimited key path, its value will be
returned instead.
```jsonc
{
"datastore.metric.host": "0.0.0.0",
"datastore": {
"metric": {
"host": "127.0.0.1"
}
}
}
```
```go
GetString("datastore.metric.host") // "0.0.0.0"
```
### Configuration Subsets
It's often useful to extract a subset of configuration (e.g., when developing a
reusable module which should accept specific sections of configuration).
```yaml
cache:
cache1:
item-size: 64
max-items: 100
cache2:
item-size: 80
max-items: 200
```
```go
func NewCache(v *Viper) *Cache {
return &Cache{
ItemSize: v.GetInt("item-size"),
MaxItems: v.GetInt("max-items"),
}
}
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)
```
### Unmarshaling
You also have the option of unmarshaling configuration to a struct, map, etc.,
using `Unmarshal*` methods.
```go
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
```
If you want to unmarshal configuration where the keys themselves contain `.`
(the default key delimiter), you can change the delimiter.
```go
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.SetDefault("chart::values", map[string]any{
"ingress": map[string]any{
"annotations": map[string]any{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
})
type config struct {
Chart struct{
Values map[string]any
}
}
var C config
v.Unmarshal(&C)
```
Viper also supports unmarshaling into embedded structs.
```go
/*
Example config:
module:
enabled: true
token: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
Module struct {
Enabled bool
moduleConfig `mapstructure:",squash"`
}
}
type moduleConfig struct {
Token string
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}
```
Viper uses
[github.com/go-viper/mapstructure](https://github.com/go-viper/mapstructure)
under the hood for unmarshaling values which uses `mapstructure` tags, by
default.
### Marshalling to String
You may need to marshal all the settings held in Viper into a string. You can
use your favorite format's marshaller with the config returned by
`AllSettings`.
```go
import (
yaml "go.yaml.in/yaml/v3"
)
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)
}
```
### Decoding Custom Formats
A frequently requested feature is adding more value formats and decoders (for
example; parsing character delimited strings into slices. This is already
available in Viper using mapstructure decode hooks.
Read more in [this blog
post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/).
## FAQ
### Why is it called “Viper”?
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.
### I found a bug or want a feature, should I file an issue or a PR?
Yes, but there are two things to be aware of.
1. The Viper project is currently prioritizing backwards compatibility and
stability over features.
2. Features may be deferred until Viper 2 forms.
### Can multiple Viper instances be used?
**tl;dr:** Yes.
Each will have its own unique configuration and can read from a different
configuration source. All of the functions that the Viper package supports are
mirrored as methods on a Viper instance.
```go
x := viper.New()
y := viper.New()
x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")
```
### Should Viper be a global singleton or passed around?
The best practice is to initialize a Viper instance and pass that around when
necessary.
Viper comes with a global instance (singleton) out of the box. Although it
makes setting up configuration easy, using it is generally discouraged as it
makes testing harder and can lead to unexpected behavior.
The global instance may be deprecated in the future. See
[#1855](https://github.com/spf13/viper/issues/1855) for more details.
### Does Viper support case sensitive keys?
**tl;dr:** No.
Viper merges configuration from various sources, many of which are either case
insensitive or use different casing than other sources (e.g., env vars). In
order to provide the best experience when using multiple sources, all keys are
made case insensitive.
There has been several attempts to implement case sensitivity, but
unfortunately it's not 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 instance?
No, you will need to synchronize access to Viper yourself (for example by using
the `sync` package). Concurrent reads and writes can cause a panic.
## Troubleshooting
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
## Development
**For an optimal developer experience, it is recommended to install
[Nix](https://nixos.org/download.html) and
[direnv](https://direnv.net/docs/installation.html).**
_Alternatively, install [Go](https://go.dev/dl/) on your computer then run
`make deps` to install the rest of the dependencies._
Run the test suite:
```shell
make test
```
Run linters:
```shell
make lint # pass -j option to run them in parallel
```
Some linter violations can automatically be fixed:
```shell
make fmt
```
## License
The project is licensed under the [MIT License](LICENSE).
================================================
FILE: TROUBLESHOOTING.md
================================================
# 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://go.dev/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://go.dev/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)
================================================
FILE: UPGRADE.md
================================================
# Update Log
**This document details any major updates required to use new features or improvements in Viper.**
## v1.20.x
### New file searching API
Viper now includes a new file searching API that allows users to customize how Viper looks for config files.
Viper accepts a custom [`Finder`](https://pkg.go.dev/github.com/spf13/viper#Finder) interface implementation:
```go
// Finder looks for files and directories in an [afero.Fs] filesystem.
type Finder interface {
Find(fsys afero.Fs) ([]string, error)
}
```
It is supposed to return a list of paths to config files.
The default implementation uses [github.com/sagikazarmark/locafero](https://github.com/sagikazarmark/locafero) under the hood.
You can supply your own implementation using `WithFinder`:
```go
v := viper.NewWithOptions(
viper.WithFinder(&MyFinder{}),
)
```
For more information, check out the [Finder examples](https://pkg.go.dev/github.com/spf13/viper#Finder)
and the [documentation](https://pkg.go.dev/github.com/sagikazarmark/locafero) for the locafero package.
### New encoding API
Viper now allows customizing the encoding layer by providing an API for encoding and decoding configuration data:
```go
// Encoder encodes Viper's internal data structures into a byte representation.
// It's primarily used for encoding a map[string]any into a file format.
type Encoder interface {
Encode(v map[string]any) ([]byte, error)
}
// Decoder decodes the contents of a byte slice into Viper's internal data structures.
// It's primarily used for decoding contents of a file into a map[string]any.
type Decoder interface {
Decode(b []byte, v map[string]any) error
}
// Codec combines [Encoder] and [Decoder] interfaces.
type Codec interface {
Encoder
Decoder
}
```
By default, Viper includes the following codecs:
- JSON
- TOML
- YAML
- Dotenv
The rest of the codecs are moved to [github.com/go-viper/encoding](https://github.com/go-viper/encoding)
Customizing the encoding layer is possible by providing a custom registry of codecs:
- [Encoder](https://pkg.go.dev/github.com/spf13/viper#Encoder) -> [EncoderRegistry](https://pkg.go.dev/github.com/spf13/viper#EncoderRegistry)
- [Decoder](https://pkg.go.dev/github.com/spf13/viper#Decoder) -> [DecoderRegistry](https://pkg.go.dev/github.com/spf13/viper#DecoderRegistry)
- [Codec](https://pkg.go.dev/github.com/spf13/viper#Codec) -> [CodecRegistry](https://pkg.go.dev/github.com/spf13/viper#CodecRegistry)
You can supply the registry of codecs to Viper using the appropriate `With*Registry` function:
```go
codecRegistry := viper.NewCodecRegistry()
codecRegistry.RegisterCodec("myformat", &MyCodec{})
v := viper.NewWithOptions(
viper.WithCodecRegistry(codecRegistry),
)
```
### BREAKING: "github.com/mitchellh/mapstructure" depedency replaced
The original [mapstructure](https://github.com/mitchellh/mapstructure) has been [archived](https://github.com/mitchellh/mapstructure/issues/349) and was replaced with a [fork](https://github.com/go-viper/mapstructure) maintained by Viper ([#1723](https://github.com/spf13/viper/pull/1723)).
As a result, the package import path needs to be changed in cases where `mapstructure` is directly referenced in your code.
For example, when providing a custom decoder config:
```go
err := viper.Unmarshal(&appConfig, func(config *mapstructure.DecoderConfig) {
config.TagName = "yaml"
})
```
The change is fairly straightforward, just replace all occurrences of the import path `github.com/mitchellh/mapstructure` with `github.com/go-viper/mapstructure/v2`:
```diff
- import "github.com/mitchellh/mapstructure"
+ import "github.com/go-viper/mapstructure/v2"
```
### BREAKING: HCL, Java properties, INI removed from core
In order to reduce third-party dependencies, Viper dropped support for the following formats from the core:
- HCL
- Java properties
- INI
You can still use these formats though by importing them from [github.com/go-viper/encoding](https://github.com/go-viper/encoding):
```go
import (
"github.com/go-viper/encoding/hcl"
"github.com/go-viper/encoding/javaproperties"
"github.com/go-viper/encoding/ini"
)
codecRegistry := viper.NewCodecRegistry()
{
codec := hcl.Codec{}
codecRegistry.RegisterCodec("hcl", codec)
codecRegistry.RegisterCodec("tfvars", codec)
}
{
codec := &javaproperties.Codec{}
codecRegistry.RegisterCodec("properties", codec)
codecRegistry.RegisterCodec("props", codec)
codecRegistry.RegisterCodec("prop", codec)
}
codecRegistry.RegisterCodec("ini", ini.Codec{})
v := viper.NewWithOptions(
viper.WithCodecRegistry(codecRegistry),
)
```
================================================
FILE: encoding.go
================================================
package viper
import (
"errors"
"strings"
"sync"
"github.com/spf13/viper/internal/encoding/dotenv"
"github.com/spf13/viper/internal/encoding/json"
"github.com/spf13/viper/internal/encoding/toml"
"github.com/spf13/viper/internal/encoding/yaml"
)
// Encoder encodes Viper's internal data structures into a byte representation.
// It's primarily used for encoding a map[string]any into a file format.
type Encoder interface {
Encode(v map[string]any) ([]byte, error)
}
// Decoder decodes the contents of a byte slice into Viper's internal data structures.
// It's primarily used for decoding contents of a file into a map[string]any.
type Decoder interface {
Decode(b []byte, v map[string]any) error
}
// Codec combines [Encoder] and [Decoder] interfaces.
type Codec interface {
Encoder
Decoder
}
// TODO: consider adding specific errors for not found scenarios
// EncoderRegistry returns an [Encoder] for a given format.
//
// Format is case-insensitive.
//
// [EncoderRegistry] returns an error if no [Encoder] is registered for the format.
type EncoderRegistry interface {
Encoder(format string) (Encoder, error)
}
// DecoderRegistry returns an [Decoder] for a given format.
//
// Format is case-insensitive.
//
// [DecoderRegistry] returns an error if no [Decoder] is registered for the format.
type DecoderRegistry interface {
Decoder(format string) (Decoder, error)
}
// CodecRegistry combines [EncoderRegistry] and [DecoderRegistry] interfaces.
type CodecRegistry interface {
EncoderRegistry
DecoderRegistry
}
// WithEncoderRegistry sets a custom [EncoderRegistry].
func WithEncoderRegistry(r EncoderRegistry) Option {
return optionFunc(func(v *Viper) {
if r == nil {
return
}
v.encoderRegistry = r
})
}
// WithDecoderRegistry sets a custom [DecoderRegistry].
func WithDecoderRegistry(r DecoderRegistry) Option {
return optionFunc(func(v *Viper) {
if r == nil {
return
}
v.decoderRegistry = r
})
}
// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry].
func WithCodecRegistry(r CodecRegistry) Option {
return optionFunc(func(v *Viper) {
if r == nil {
return
}
v.encoderRegistry = r
v.decoderRegistry = r
})
}
// DefaultCodecRegistry is a simple implementation of [CodecRegistry] that allows registering custom [Codec]s.
type DefaultCodecRegistry struct {
codecs map[string]Codec
mu sync.RWMutex
once sync.Once
}
// NewCodecRegistry returns a new [CodecRegistry], ready to accept custom [Codec]s.
func NewCodecRegistry() *DefaultCodecRegistry {
r := &DefaultCodecRegistry{}
r.init()
return r
}
func (r *DefaultCodecRegistry) init() {
r.once.Do(func() {
r.codecs = map[string]Codec{}
})
}
// RegisterCodec registers a custom [Codec].
//
// Format is case-insensitive.
func (r *DefaultCodecRegistry) RegisterCodec(format string, codec Codec) error {
r.init()
r.mu.Lock()
defer r.mu.Unlock()
r.codecs[strings.ToLower(format)] = codec
return nil
}
// Encoder implements the [EncoderRegistry] interface.
//
// Format is case-insensitive.
func (r *DefaultCodecRegistry) Encoder(format string) (Encoder, error) {
encoder, ok := r.codec(format)
if !ok {
return nil, errors.New("encoder not found for this format")
}
return encoder, nil
}
// Decoder implements the [DecoderRegistry] interface.
//
// Format is case-insensitive.
func (r *DefaultCodecRegistry) Decoder(format string) (Decoder, error) {
decoder, ok := r.codec(format)
if !ok {
return nil, errors.New("decoder not found for this format")
}
return decoder, nil
}
func (r *DefaultCodecRegistry) codec(format string) (Codec, bool) {
r.mu.Lock()
defer r.mu.Unlock()
format = strings.ToLower(format)
if r.codecs != nil {
codec, ok := r.codecs[format]
if ok {
return codec, true
}
}
switch format {
case "yaml", "yml":
return yaml.Codec{}, true
case "json":
return json.Codec{}, true
case "toml":
return toml.Codec{}, true
case "dotenv", "env":
return &dotenv.Codec{}, true
}
return nil, false
}
================================================
FILE: encoding_test.go
================================================
package viper
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type codec struct{}
func (codec) Encode(_ map[string]any) ([]byte, error) {
return nil, nil
}
func (codec) Decode(_ []byte, _ map[string]any) error {
return nil
}
func TestDefaultCodecRegistry(t *testing.T) {
t.Run("OK", func(t *testing.T) {
registry := NewCodecRegistry()
c := codec{}
err := registry.RegisterCodec("myformat", c)
require.NoError(t, err)
encoder, err := registry.Encoder("myformat")
require.NoError(t, err)
assert.Equal(t, c, encoder)
decoder, err := registry.Decoder("myformat")
require.NoError(t, err)
assert.Equal(t, c, decoder)
})
t.Run("CodecNotFound", func(t *testing.T) {
registry := NewCodecRegistry()
_, err := registry.Encoder("myformat")
require.Error(t, err)
_, err = registry.Decoder("myformat")
require.Error(t, err)
})
t.Run("FormatIsCaseInsensitive", func(t *testing.T) {
registry := NewCodecRegistry()
c := codec{}
err := registry.RegisterCodec("MYFORMAT", c)
require.NoError(t, err)
{
encoder, err := registry.Encoder("myformat")
require.NoError(t, err)
assert.Equal(t, c, encoder)
}
{
encoder, err := registry.Encoder("MYFORMAT")
require.NoError(t, err)
assert.Equal(t, c, encoder)
}
{
decoder, err := registry.Decoder("myformat")
require.NoError(t, err)
assert.Equal(t, c, decoder)
}
{
decoder, err := registry.Decoder("MYFORMAT")
require.NoError(t, err)
assert.Equal(t, c, decoder)
}
})
t.Run("OverrideDefault", func(t *testing.T) {
registry := NewCodecRegistry()
c := codec{}
err := registry.RegisterCodec("yaml", c)
require.NoError(t, err)
encoder, err := registry.Encoder("yaml")
require.NoError(t, err)
assert.Equal(t, c, encoder)
decoder, err := registry.Decoder("yaml")
require.NoError(t, err)
assert.Equal(t, c, decoder)
})
}
================================================
FILE: errors.go
================================================
package viper
import (
"fmt"
)
// FileLookupError is returned when Viper cannot resolve a configuration file.
//
// This is meant to be a common interface for all file look-up errors, occurring either because a
// file does not exist or because it cannot find any file matching finder criteria.
type FileLookupError interface {
error
fileLookup()
}
// ConfigFileNotFoundError denotes failing to find a configuration file from a search.
//
// Deprecated: This is error wraps [FileNotFoundFromSearchError], which should be used instead.
type ConfigFileNotFoundError struct {
locations []string
name string
}
// Error returns the formatted error.
func (e ConfigFileNotFoundError) Error() string {
return e.Unwrap().Error()
}
// Unwraps to FileNotFoundFromSearchError.
func (e ConfigFileNotFoundError) Unwrap() error {
return FileNotFoundFromSearchError(e)
}
// FileNotFoundFromSearchError denotes failing to find a configuration file from a search.
// Wraps ConfigFileNotFoundError.
type FileNotFoundFromSearchError struct {
locations []string
name string
}
func (e FileNotFoundFromSearchError) fileLookup() {}
// Error returns the formatted error.
func (e FileNotFoundFromSearchError) Error() string {
message := fmt.Sprintf("File %q not found", e.name)
if len(e.locations) > 0 {
message += fmt.Sprintf(" in %v", e.locations)
}
return message
}
// FileNotFoundError denotes failing to find a specific configuration file.
type FileNotFoundError struct {
err error
path string
}
func (e FileNotFoundError) fileLookup() {}
// Error returns the formatted error.
func (e FileNotFoundError) Error() string {
return fmt.Sprintf("file not found: %s", e.path)
}
// ConfigFileAlreadyExistsError denotes failure to write new configuration file.
type ConfigFileAlreadyExistsError string
// Error returns the formatted error when configuration already exists.
func (e ConfigFileAlreadyExistsError) Error() string {
return fmt.Sprintf("Config File %q Already Exists", string(e))
}
// ConfigMarshalError happens when failing to marshal the configuration.
type ConfigMarshalError struct {
err error
}
// Error returns the formatted configuration error.
func (e ConfigMarshalError) Error() string {
return fmt.Sprintf("While marshaling config: %s", e.err.Error())
}
// UnsupportedConfigError denotes encountering an unsupported
// configuration filetype.
type UnsupportedConfigError string
// Error returns the formatted configuration error.
func (str UnsupportedConfigError) Error() string {
return fmt.Sprintf("Unsupported Config Type %q", string(str))
}
================================================
FILE: experimental.go
================================================
package viper
// ExperimentalBindStruct tells Viper to use the new bind struct feature.
func ExperimentalBindStruct() Option {
return optionFunc(func(v *Viper) {
v.experimentalBindStruct = true
})
}
================================================
FILE: file.go
================================================
package viper
import (
"os"
"path/filepath"
"github.com/sagikazarmark/locafero"
"github.com/spf13/afero"
)
// ExperimentalFinder tells Viper to use the new Finder interface for finding configuration files.
func ExperimentalFinder() Option {
return optionFunc(func(v *Viper) {
v.experimentalFinder = true
})
}
// Search for a config file.
func (v *Viper) findConfigFile() (string, error) {
finder := v.finder
if finder == nil && v.experimentalFinder {
var names []string
if v.configType != "" {
names = locafero.NameWithOptionalExtensions(v.configName, SupportedExts...)
} else {
names = locafero.NameWithExtensions(v.configName, SupportedExts...)
}
finder = locafero.Finder{
Paths: v.configPaths,
Names: names,
Type: locafero.FileTypeFile,
}
}
if finder != nil {
return v.findConfigFileWithFinder(finder)
}
return v.findConfigFileOld()
}
func (v *Viper) findConfigFileWithFinder(finder Finder) (string, error) {
results, err := finder.Find(v.fs)
if err != nil {
return "", err
}
if len(results) == 0 {
return "", ConfigFileNotFoundError{name: v.configName, locations: v.configPaths}
}
// We call clean on the final result to ensure that the path is in its canonical form.
// This is mostly for consistent path handling and to make sure tests pass.
return results[0], nil
}
// Search all configPaths for any config file.
// Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFileOld() (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{name: v.configName, locations: 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 ""
}
// exists checks 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
}
================================================
FILE: finder.go
================================================
package viper
import (
"errors"
"github.com/spf13/afero"
)
// WithFinder sets a custom [Finder].
func WithFinder(f Finder) Option {
return optionFunc(func(v *Viper) {
if f == nil {
return
}
v.finder = f
})
}
// Finder looks for files and directories in an [afero.Fs] filesystem.
type Finder interface {
Find(fsys afero.Fs) ([]string, error)
}
// Finders combines multiple finders into one.
func Finders(finders ...Finder) Finder {
return &combinedFinder{finders: finders}
}
// combinedFinder is a Finder that combines multiple finders.
type combinedFinder struct {
finders []Finder
}
// Find implements the [Finder] interface.
func (c *combinedFinder) Find(fsys afero.Fs) ([]string, error) {
var results []string
var errs []error
for _, finder := range c.finders {
if finder == nil {
continue
}
r, err := finder.Find(fsys)
if err != nil {
errs = append(errs, err)
continue
}
results = append(results, r...)
}
return results, errors.Join(errs...)
}
================================================
FILE: finder_example_test.go
================================================
package viper_test
import (
"fmt"
"github.com/sagikazarmark/locafero"
"github.com/spf13/afero"
"github.com/spf13/viper"
)
func ExampleFinder() {
fs := afero.NewMemMapFs()
fs.Mkdir("/home/user", 0o777)
f, _ := fs.Create("/home/user/myapp.yaml")
f.WriteString("foo: bar")
f.Close()
// HCL will have a "lower" priority in the search order
fs.Create("/home/user/myapp.hcl")
finder := locafero.Finder{
Paths: []string{"/home/user"},
Names: locafero.NameWithExtensions("myapp", viper.SupportedExts...),
Type: locafero.FileTypeFile, // This is important!
}
v := viper.NewWithOptions(viper.WithFinder(finder))
v.SetFs(fs)
v.ReadInConfig()
fmt.Println(v.GetString("foo"))
// Output:
// bar
}
func ExampleFinders() {
fs := afero.NewMemMapFs()
fs.Mkdir("/home/user", 0o777)
f, _ := fs.Create("/home/user/myapp.yaml")
f.WriteString("foo: bar")
f.Close()
fs.Mkdir("/etc/myapp", 0o777)
fs.Create("/etc/myapp/config.yaml")
// Combine multiple finders to search for files in multiple locations with different criteria
finder := viper.Finders(
locafero.Finder{
Paths: []string{"/home/user"},
Names: locafero.NameWithExtensions("myapp", viper.SupportedExts...),
Type: locafero.FileTypeFile, // This is important!
},
locafero.Finder{
Paths: []string{"/etc/myapp"},
Names: []string{"config.yaml"}, // Only accept YAML files in the system config directory
Type: locafero.FileTypeFile, // This is important!
},
)
v := viper.NewWithOptions(viper.WithFinder(finder))
v.SetFs(fs)
v.ReadInConfig()
fmt.Println(v.GetString("foo"))
// Output:
// bar
}
================================================
FILE: finder_test.go
================================================
package viper
import (
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type finderStub struct {
results []string
}
func (f finderStub) Find(_ afero.Fs) ([]string, error) {
return f.results, nil
}
func TestFinders(t *testing.T) {
finder := Finders(
finderStub{
results: []string{
"/home/user/.viper.yaml",
},
},
finderStub{
results: []string{
"/etc/viper/config.yaml",
},
},
)
results, err := finder.Find(afero.NewMemMapFs())
require.NoError(t, err)
expected := []string{
"/home/user/.viper.yaml",
"/etc/viper/config.yaml",
}
assert.Equal(t, expected, results)
}
================================================
FILE: flags.go
================================================
package viper
import "github.com/spf13/pflag"
// FlagValueSet is an interface that users can implement
// to bind a set of flags to viper.
type FlagValueSet interface {
VisitAll(fn func(FlagValue))
}
// FlagValue is an interface that users can implement
// to bind different flags to viper.
type FlagValue interface {
HasChanged() bool
Name() string
ValueString() string
ValueType() string
}
// pflagValueSet is a wrapper around *pflag.ValueSet
// that implements FlagValueSet.
type pflagValueSet struct {
flags *pflag.FlagSet
}
// VisitAll iterates over all *pflag.Flag inside the *pflag.FlagSet.
func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) {
p.flags.VisitAll(func(flag *pflag.Flag) {
fn(pflagValue{flag})
})
}
// pflagValue is a wrapper around *pflag.flag
// that implements FlagValue.
type pflagValue struct {
flag *pflag.Flag
}
// HasChanged returns whether the flag has changes or not.
func (p pflagValue) HasChanged() bool {
return p.flag.Changed
}
// Name returns the name of the flag.
func (p pflagValue) Name() string {
return p.flag.Name
}
// ValueString returns the value of the flag as a string.
func (p pflagValue) ValueString() string {
return p.flag.Value.String()
}
// ValueType returns the type of the flag as a string.
func (p pflagValue) ValueType() string {
return p.flag.Value.Type()
}
================================================
FILE: flags_test.go
================================================
package viper
import (
"testing"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBindFlagValueSet(t *testing.T) {
Reset()
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
testValues := map[string]*string{
"host": nil,
"port": nil,
"endpoint": nil,
}
mutatedTestValues := map[string]string{
"host": "localhost",
"port": "6060",
"endpoint": "/public",
}
for name := range testValues {
testValues[name] = flagSet.String(name, "", "test")
}
flagValueSet := pflagValueSet{flagSet}
err := BindFlagValues(flagValueSet)
require.NoError(t, err, "error binding flag set")
flagSet.VisitAll(func(flag *pflag.Flag) {
flag.Value.Set(mutatedTestValues[flag.Name])
flag.Changed = true
})
for name, expected := range mutatedTestValues {
assert.Equal(t, expected, Get(name))
}
}
func TestBindFlagValue(t *testing.T) {
testString := "testing"
testValue := newStringValue(testString, &testString)
flag := &pflag.Flag{
Name: "testflag",
Value: testValue,
Changed: false,
}
flagValue := pflagValue{flag}
BindFlagValue("testvalue", flagValue)
assert.Equal(t, testString, Get("testvalue"))
flag.Value.Set("testing_mutate")
flag.Changed = true // hack for pflag usage
assert.Equal(t, "testing_mutate", Get("testvalue"))
}
================================================
FILE: flake.nix
================================================
{
description = "Viper";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
devenv.url = "github:cachix/devenv";
};
outputs =
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devenv.flakeModule
];
systems = [
"x86_64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem =
{ pkgs, ... }:
{
devenv.shells = {
default = {
languages = {
go.enable = true;
};
git-hooks.hooks = {
nixpkgs-fmt.enable = true;
yamllint.enable = true;
};
packages = with pkgs; [
gnumake
golangci-lint
yamllint
];
scripts = {
versions.exec = ''
go version
golangci-lint version
'';
};
enterShell = ''
versions
'';
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
containers = pkgs.lib.mkForce { };
};
};
};
};
}
================================================
FILE: go.mod
================================================
module github.com/spf13/viper
go 1.23.0
require (
github.com/fsnotify/fsnotify v1.9.0
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/sagikazarmark/locafero v0.12.0
github.com/spf13/afero v1.15.0
github.com/spf13/cast v1.10.0
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/subosito/gotenv v1.6.0
go.yaml.in/yaml/v3 v3.0.4
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/encoding/dotenv/codec.go
================================================
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{}
// Encode encodes a map[string]any into a dotenv byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
flattened := map[string]any{}
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
}
// Decode decodes a dotenv byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) 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
}
================================================
FILE: internal/encoding/dotenv/codec_test.go
================================================
package dotenv
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// original form of the data.
const original = `# key-value pair
KEY=value
`
// encoded form of the data.
const encoded = `KEY=value
`
// data is Viper's internal representation.
var data = map[string]any{
"KEY": "value",
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
require.NoError(t, err)
assert.Equal(t, encoded, string(b))
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(original), v)
require.NoError(t, err)
assert.Equal(t, data, v)
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(`invalid data`), v)
require.Error(t, err)
t.Logf("decoding failed as expected: %s", err)
})
}
================================================
FILE: internal/encoding/dotenv/map_utils.go
================================================
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 the main package.
// TODO: move it to a common place.
func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any {
if shadow != nil && prefix != "" && shadow[prefix] != nil {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]any)
}
var m2 map[string]any
if prefix != "" {
prefix += delimiter
}
for k, val := range m {
fullKey := prefix + k
switch val := val.(type) {
case map[string]any:
m2 = val
case map[any]any:
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
}
================================================
FILE: internal/encoding/json/codec.go
================================================
package json
import (
"encoding/json"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
type Codec struct{}
// Encode encodes a map[string]any into a JSON byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
// TODO: expose prefix and indent in the Codec as setting?
return json.MarshalIndent(v, "", " ")
}
// Decode decodes a JSON byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return json.Unmarshal(b, &v)
}
================================================
FILE: internal/encoding/json/codec_test.go
================================================
package json
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// 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"
]
}
}
}`
// data is Viper's internal representation.
var data = map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
"map": map[string]any{
"key": "value",
},
"nested_map": map[string]any{
"map": map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
require.NoError(t, err)
assert.JSONEq(t, encoded, string(b))
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(encoded), v)
require.NoError(t, err)
assert.Equal(t, data, v)
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(`invalid data`), v)
require.Error(t, err)
t.Logf("decoding failed as expected: %s", err)
})
}
================================================
FILE: internal/encoding/toml/codec.go
================================================
package toml
import (
"github.com/pelletier/go-toml/v2"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
type Codec struct{}
// Encode encodes a map[string]any into a TOML byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
return toml.Marshal(v)
}
// Decode decodes a TOML byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return toml.Unmarshal(b, &v)
}
================================================
FILE: internal/encoding/toml/codec_test.go
================================================
package toml
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// 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']
`
// data is Viper's internal representation.
var data = map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
"map": map[string]any{
"key": "value",
},
"nested_map": map[string]any{
"map": map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
require.NoError(t, err)
assert.Equal(t, encoded, string(b))
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(original), v)
require.NoError(t, err)
assert.Equal(t, data, v)
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(`invalid data`), v)
require.Error(t, err)
t.Logf("decoding failed as expected: %s", err)
})
}
================================================
FILE: internal/encoding/yaml/codec.go
================================================
package yaml
import "go.yaml.in/yaml/v3"
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
type Codec struct{}
// Encode encodes a map[string]any into a YAML byte slice.
func (Codec) Encode(v map[string]any) ([]byte, error) {
return yaml.Marshal(v)
}
// Decode decodes a YAML byte slice into a map[string]any.
func (Codec) Decode(b []byte, v map[string]any) error {
return yaml.Unmarshal(b, &v)
}
================================================
FILE: internal/encoding/yaml/codec_test.go
================================================
package yaml
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// 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
// (e.g. map is decoded into a map with interface key).
var decoded = map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
"map": map[string]any{
"key": "value",
},
"nested_map": map[string]any{
"map": map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
},
},
}
// data is Viper's internal representation.
var data = map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
"map": map[string]any{
"key": "value",
},
"nested_map": map[string]any{
"map": map[string]any{
"key": "value",
"list": []any{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
require.NoError(t, err)
assert.Equal(t, encoded, string(b))
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(original), v)
require.NoError(t, err)
assert.Equal(t, decoded, v)
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]any{}
err := codec.Decode([]byte(`invalid data`), v)
require.Error(t, err)
t.Logf("decoding failed as expected: %s", err)
})
}
================================================
FILE: internal/features/bind_struct.go
================================================
//go:build viper_bind_struct
package features
// BindStruct is a feature flag for enabling/disabling the config binding to structs.
const BindStruct = true
================================================
FILE: internal/features/bind_struct_default.go
================================================
//go:build !viper_bind_struct
package features
// BindStruct is a feature flag for enabling/disabling the config binding to structs.
const BindStruct = false
================================================
FILE: internal/features/finder.go
================================================
//go:build viper_finder
package features
// Finder is a feature flag for enabling/disabling the config finder.
const Finder = true
================================================
FILE: internal/features/finder_default.go
================================================
//go:build !viper_finder
package features
// Finder is a feature flag for enabling/disabling the config finder.
const Finder = false
================================================
FILE: internal/testutil/filepath.go
================================================
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
}
================================================
FILE: logger.go
================================================
package viper
import (
"context"
"log/slog"
)
// WithLogger sets a custom logger.
func WithLogger(l *slog.Logger) Option {
return optionFunc(func(v *Viper) {
v.logger = l
})
}
type discardHandler struct{}
func (n *discardHandler) Enabled(_ context.Context, _ slog.Level) bool {
return false
}
func (n *discardHandler) Handle(_ context.Context, _ slog.Record) error {
return nil
}
func (n *discardHandler) WithAttrs(_ []slog.Attr) slog.Handler {
return n
}
func (n *discardHandler) WithGroup(_ string) slog.Handler {
return n
}
================================================
FILE: overrides_test.go
================================================
package viper
import (
"strings"
"testing"
"github.com/spf13/cast"
"github.com/stretchr/testify/assert"
)
type layer int
const (
defaultLayer layer = iota + 1
overrideLayer
)
func TestNestedOverrides(t *testing.T) {
assert := assert.New(t)
var v *Viper
// Case 0: value overridden by a value
overrideDefault(assert, "tom", 10, "tom", 20) // "tom" is first given 10 as default value, then overridden by 20
override(assert, "tom", 10, "tom", 20) // "tom" is first given value 10, then overridden by 20
overrideDefault(assert, "tom.age", 10, "tom.age", 20)
override(assert, "tom.age", 10, "tom.age", 20)
overrideDefault(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)
override(assert, "sawyer.tom.age", 10, "sawyer.tom.age", 20)
// Case 1: key:value overridden by a value
v = overrideDefault(assert, "tom.age", 10, "tom", "boy") // "tom.age" is first given 10 as default value, then "tom" is overridden by "boy"
assert.Nil(v.Get("tom.age")) // "tom.age" should not exist anymore
v = override(assert, "tom.age", 10, "tom", "boy")
assert.Nil(v.Get("tom.age"))
// Case 2: value overridden by a key:value
overrideDefault(assert, "tom", "boy", "tom.age", 10) // "tom" is first given "boy" as default value, then "tom" is overridden by map{"age":10}
override(assert, "tom.age", 10, "tom", "boy")
// Case 3: key:value overridden by a key:value
v = overrideDefault(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size")) // value should still be reachable
v = override(assert, "tom.size", 4, "tom.age", 10)
assert.Equal(4, v.Get("tom.size"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "size"}, 4)
// Case 4: key:value overridden by a map
v = overrideDefault(assert, "tom.size", 4, "tom", map[string]any{"age": 10}) // "tom.size" is first given "4" as default value, then "tom" is overridden by map{"age":10}
assert.Equal(4, v.Get("tom.size")) // "tom.size" should still be reachable
assert.Equal(10, v.Get("tom.age")) // new value should be there
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10) // new value should be there
v = override(assert, "tom.size", 4, "tom", map[string]any{"age": 10})
assert.Nil(v.Get("tom.size"))
assert.Equal(10, v.Get("tom.age"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, 10)
// Case 5: array overridden by a value
overrideDefault(assert, "tom", []int{10, 20}, "tom", 30)
override(assert, "tom", []int{10, 20}, "tom", 30)
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", 30)
override(assert, "tom.age", []int{10, 20}, "tom.age", 30)
// Case 6: array overridden by an array
overrideDefault(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
override(assert, "tom", []int{10, 20}, "tom", []int{30, 40})
overrideDefault(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
v = override(assert, "tom.age", []int{10, 20}, "tom.age", []int{30, 40})
// explicit array merge:
s, ok := v.Get("tom.age").([]int)
if assert.True(ok, "tom[\"age\"] is not a slice") {
v.Set("tom.age", append(s, []int{50, 60}...))
assert.Equal([]int{30, 40, 50, 60}, v.Get("tom.age"))
deepCheckValue(assert, v, overrideLayer, []string{"tom", "age"}, []int{30, 40, 50, 60})
}
}
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
func override(assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
}
// overrideFromLayer performs the sequential override and low-level checks.
//
// First assignment is made on layer l for path firstPath with value firstValue,
// the second one on the override layer (i.e., with the Set() function)
// for path secondPath with value secondValue.
//
// firstPath and secondPath can include an arbitrary number of dots to indicate
// a nested element.
//
// After each assignment, the value is checked, retrieved both by its full path
// and by its key sequence (successive maps).
func overrideFromLayer(l layer, assert *assert.Assertions, firstPath string, firstValue any, secondPath string, secondValue any) *Viper {
v := New()
firstKeys := strings.Split(firstPath, v.keyDelim)
if assert == nil ||
len(firstKeys) == 0 || firstKeys[0] == "" {
return v
}
// Set and check first value
switch l {
case defaultLayer:
v.SetDefault(firstPath, firstValue)
case overrideLayer:
v.Set(firstPath, firstValue)
default:
return v
}
assert.Equal(firstValue, v.Get(firstPath))
deepCheckValue(assert, v, l, firstKeys, firstValue)
// Override and check new value
secondKeys := strings.Split(secondPath, v.keyDelim)
if len(secondKeys) == 0 || secondKeys[0] == "" {
return v
}
v.Set(secondPath, secondValue)
assert.Equal(secondValue, v.Get(secondPath))
deepCheckValue(assert, v, overrideLayer, secondKeys, secondValue)
return v
}
// deepCheckValue checks that all given keys correspond to a valid path in the
// configuration map of the given layer, and that the final value equals the one given.
func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys []string, value any) {
if assert == nil || v == nil ||
len(keys) == 0 || keys[0] == "" {
return
}
// init
var val any
var ms string
switch l {
case defaultLayer:
val = v.defaults
ms = "v.defaults"
case overrideLayer:
val = v.override
ms = "v.override"
}
// loop through map
var m map[string]any
for _, k := range keys {
if val == nil {
assert.Failf("%s is not a map[string]any", ms)
return
}
// deep scan of the map to get the final value
switch val := val.(type) {
case map[any]any:
m = cast.ToStringMap(val)
case map[string]any:
m = val
default:
assert.Failf("%s is not a map[string]any", ms)
return
}
ms = ms + "[\"" + k + "\"]"
val = m[k]
}
assert.Equal(value, val)
}
================================================
FILE: remote/go.mod
================================================
module github.com/spf13/viper/remote
go 1.23.8
replace github.com/spf13/viper => ../
require (
github.com/sagikazarmark/crypt v0.31.0
github.com/spf13/viper v1.21.0
)
require (
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/firestore v1.18.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hashicorp/consul/api v1.32.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.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/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/nats.go v1.45.0 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.etcd.io/etcd/api/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/v2 v2.305.22 // indirect
go.etcd.io/etcd/client/v3 v3.6.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.7 // indirect
)
================================================
FILE: remote/go.sum
================================================
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/firestore v1.18.0 h1:cuydCaLS7Vl2SatAeivXyhbhDEIR8BDmtn4egDhIn2s=
cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/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.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/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/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.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/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.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.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/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.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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/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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
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.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/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
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.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/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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/nats-io/nats.go v1.45.0 h1:/wGPbnYXDM0pLKFjZTX+2JOw9TQPoIgTFrUaH97giwA=
github.com/nats-io/nats.go v1.45.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
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.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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 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_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.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/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.31.0 h1:JJLrH7UojwA5KBkWuuk9x6UgHMzBaU2J2RHpEzUlpAc=
github.com/sagikazarmark/crypt v0.31.0/go.mod h1:X8SJJi7WiZU/Rgdr//EtoELirhl3vah7L7/fcBsO5Hk=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0=
go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
go.etcd.io/etcd/client/v2 v2.305.22 h1:FedDsGxor5iE9muhXm1CgE/TiSVOtgyB5+NYCHPzA2Q=
go.etcd.io/etcd/client/v2 v2.305.22/go.mod h1:VP7+1hEKyfGPuRdDmtT8GjM2HcVCKVlGmxfv3NwmrII=
go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A=
go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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-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-20201020160332-67f06af15bc9/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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-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-20191026070338-33540a1f6037/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-20200223170610-d5e6a3e2c0ae/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: remote/remote.go
================================================
// Copyright © 2015 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Package remote integrates the remote features of Viper.
package remote
import (
"bytes"
"io"
"os"
"strings"
crypt "github.com/sagikazarmark/crypt/config"
"github.com/spf13/viper"
)
type remoteConfigProvider struct{}
func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
cm, err := getConfigManager(rp)
if err != nil {
return nil, err
}
b, err := cm.Get(rp.Path())
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
cm, err := getConfigManager(rp)
if err != nil {
return nil, err
}
resp, err := cm.Get(rp.Path())
if err != nil {
return nil, err
}
return bytes.NewReader(resp), nil
}
func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) {
cm, err := getConfigManager(rp)
if err != nil {
return nil, nil
}
quit := make(chan bool)
quitwc := make(chan bool)
viperResponsCh := make(chan *viper.RemoteResponse)
cryptoResponseCh := cm.Watch(rp.Path(), quit)
// need this function to convert the Channel response form crypt.Response to viper.Response
go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) {
for {
select {
case <-quitwc:
quit <- true
return
case resp := <-cr:
vr <- &viper.RemoteResponse{
Error: resp.Error,
Value: resp.Value,
}
}
}
}(cryptoResponseCh, viperResponsCh, quitwc, quit)
return viperResponsCh, quitwc
}
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())
if err != nil {
return nil, err
}
defer kr.Close()
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)
case "nats":
cm, err = crypt.NewNatsConfigManager(endpoints, kr)
default:
cm, err = crypt.NewConsulConfigManager(endpoints, kr)
}
} else {
switch rp.Provider() {
case "etcd":
cm, err = crypt.NewStandardEtcdConfigManager(endpoints)
case "etcd3":
cm, err = crypt.NewStandardEtcdV3ConfigManager(endpoints)
case "firestore":
cm, err = crypt.NewStandardFirestoreConfigManager(endpoints)
case "nats":
cm, err = crypt.NewStandardNatsConfigManager(endpoints)
default:
cm, err = crypt.NewStandardConsulConfigManager(endpoints)
}
}
if err != nil {
return nil, err
}
return cm, nil
}
func init() {
viper.RemoteConfig = &remoteConfigProvider{}
}
================================================
FILE: remote.go
================================================
package viper
import (
"bytes"
"fmt"
"io"
"reflect"
"slices"
)
// SupportedRemoteProviders are universally supported remote providers.
var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"}
func resetRemote() {
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"}
}
type remoteConfigFactory interface {
Get(rp RemoteProvider) (io.Reader, error)
Watch(rp RemoteProvider) (io.Reader, error)
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool)
}
// RemoteResponse represents a response from a remote configuration provider.
type RemoteResponse struct {
Value []byte
Error error
}
// RemoteConfig is optional, see the remote package.
var RemoteConfig remoteConfigFactory
// UnsupportedRemoteProviderError denotes encountering an unsupported remote
// provider. Currently only etcd and Consul are supported.
type UnsupportedRemoteProviderError string
// Error returns the formatted remote provider error.
func (str UnsupportedRemoteProviderError) Error() string {
return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str))
}
// RemoteConfigError denotes encountering an error while trying to
// pull the configuration from the remote provider.
type RemoteConfigError string
// Error returns the formatted remote provider error.
func (rce RemoteConfigError) Error() string {
return fmt.Sprintf("Remote Configurations Error: %s", string(rce))
}
type defaultRemoteProvider struct {
provider string
endpoint string
path string
secretKeyring string
}
func (rp defaultRemoteProvider) Provider() string {
return rp.provider
}
func (rp defaultRemoteProvider) Endpoint() string {
return rp.endpoint
}
func (rp defaultRemoteProvider) Path() string {
return rp.path
}
func (rp defaultRemoteProvider) SecretKeyring() string {
return rp.secretKeyring
}
// RemoteProvider stores the configuration necessary
// to connect to a remote key/value store.
// Optional secretKeyring to unencrypt encrypted values
// can be provided.
type RemoteProvider interface {
Provider() string
Endpoint() string
Path() string
SecretKeyring() string
}
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://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
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
func AddRemoteProvider(provider, endpoint, path string) error {
return v.AddRemoteProvider(provider, endpoint, path)
}
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported.
// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://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
// you should set path to /configs and set config name (SetConfigName()) to
// "myapp".
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
if !slices.Contains(SupportedRemoteProviders, provider) {
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
path: path,
}
if !v.providerPathExists(rp) {
v.remoteProviders = append(v.remoteProviders, rp)
}
}
return nil
}
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" 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/sagikazarmark/crypt.
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring)
}
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" 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/sagikazarmark/crypt.
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error {
if !slices.Contains(SupportedRemoteProviders, provider) {
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
path: path,
secretKeyring: secretkeyring,
}
if !v.providerPathExists(rp) {
v.remoteProviders = append(v.remoteProviders, rp)
}
}
return nil
}
func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
for _, y := range v.remoteProviders {
if reflect.DeepEqual(y, p) {
return true
}
}
return false
}
// ReadRemoteConfig attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
// ReadRemoteConfig attempts to get configuration from a remote source
// and read it in the remote configuration registry.
func (v *Viper) ReadRemoteConfig() error {
return v.getKeyValueConfig()
}
// WatchRemoteConfig updates configuration from available remote providers.
func WatchRemoteConfig() error { return v.WatchRemoteConfig() }
// WatchRemoteConfig updates configuration from available remote providers.
func (v *Viper) WatchRemoteConfig() error {
return v.watchKeyValueConfig()
}
// WatchRemoteConfigOnChannel updates configuration from available remote providers.
func (v *Viper) WatchRemoteConfigOnChannel() error {
return v.watchKeyValueConfigOnChannel()
}
// 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/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")
}
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]any, error) {
reader, err := RemoteConfig.Get(provider)
if err != nil {
return nil, err
}
err = v.unmarshalReader(reader, v.kvstore)
return v.kvstore, err
}
// 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
go func(rc <-chan *RemoteResponse) {
for {
b := <-rc
reader := bytes.NewReader(b.Value)
err := v.unmarshalReader(reader, v.kvstore)
if err != nil {
v.logger.Error(fmt.Errorf("failed to unmarshal remote config: %w", err).Error())
}
}
}(respc)
return nil
}
return RemoteConfigError("No Files Found")
}
// 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
return nil
}
return RemoteConfigError("No Files Found")
}
func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]any, error) {
reader, err := RemoteConfig.Watch(provider)
if err != nil {
return nil, err
}
err = v.unmarshalReader(reader, v.kvstore)
return v.kvstore, err
}
================================================
FILE: util.go
================================================
// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Viper is a application configuration system.
// It believes that applications can be configured a variety of ways
// via flags, ENVIRONMENT variables, configuration files retrieved
// from the file system, or a remote key/value store.
package viper
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
"strings"
"unicode"
"github.com/spf13/cast"
)
// ConfigParseError denotes failing to parse configuration file.
type ConfigParseError struct {
err error
}
// Error returns the formatted configuration error.
func (pe ConfigParseError) Error() string {
return fmt.Sprintf("While parsing config: %s", pe.err.Error())
}
// Unwrap returns the wrapped error.
func (pe ConfigParseError) Unwrap() error {
return pe.err
}
// toCaseInsensitiveValue checks if the value is a map;
// if so, create a copy and lower-case the keys recursively.
func toCaseInsensitiveValue(value any) any {
switch v := value.(type) {
case map[any]any:
value = copyAndInsensitiviseMap(cast.ToStringMap(v))
case map[string]any:
value = copyAndInsensitiviseMap(v)
}
return value
}
// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of
// any map it makes case insensitive.
func copyAndInsensitiviseMap(m map[string]any) map[string]any {
nm := make(map[string]any)
for key, val := range m {
lkey := strings.ToLower(key)
switch v := val.(type) {
case map[any]any:
nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v))
case map[string]any:
nm[lkey] = copyAndInsensitiviseMap(v)
default:
nm[lkey] = v
}
}
return nm
}
func insensitiviseVal(val any) any {
switch v := val.(type) {
case map[any]any:
// nested map: cast and recursively insensitivise
val = cast.ToStringMap(val)
insensitiviseMap(val.(map[string]any))
case map[string]any:
// nested map: recursively insensitivise
insensitiviseMap(v)
case []any:
// nested array: recursively insensitivise
insensitiveArray(v)
}
return val
}
func insensitiviseMap(m map[string]any) {
for key, val := range m {
val = insensitiviseVal(val)
lower := strings.ToLower(key)
if key != lower {
// remove old key (not lower-cased)
delete(m, key)
}
// update map
m[lower] = val
}
}
func insensitiveArray(a []any) {
for i, val := range a {
a[i] = insensitiviseVal(val)
}
}
func absPathify(logger *slog.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:]
}
inPath = os.ExpandEnv(inPath)
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
}
p, err := filepath.Abs(inPath)
if err == nil {
return filepath.Clean(p)
}
logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
return ""
}
func userHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}
func safeMul(a, b uint) uint {
c := a * b
if a > 1 && b > 1 && c/b != a {
return 0
}
return c
}
// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes.
func parseSizeInBytes(sizeStr string) uint {
sizeStr = strings.TrimSpace(sizeStr)
lastChar := len(sizeStr) - 1
multiplier := uint(1)
if lastChar > 0 {
if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' {
if lastChar > 1 {
switch unicode.ToLower(rune(sizeStr[lastChar-1])) {
case 'k':
multiplier = 1 << 10
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'm':
multiplier = 1 << 20
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
case 'g':
multiplier = 1 << 30
sizeStr = strings.TrimSpace(sizeStr[:lastChar-1])
default:
multiplier = 1
sizeStr = strings.TrimSpace(sizeStr[:lastChar])
}
}
}
}
size := max(cast.ToInt(sizeStr), 0)
return safeMul(uint(size), multiplier)
}
// 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]any, path []string) map[string]any {
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]any)
m[k] = m3
m = m3
continue
}
m3, ok := m2.(map[string]any)
if !ok {
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]any)
m[k] = m3
}
// continue search from here
m = m3
}
return m
}
================================================
FILE: util_test.go
================================================
// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Viper is a application configuration system.
// It believes that applications can be configured a variety of ways
// via flags, ENVIRONMENT variables, configuration files retrieved
// from the file system, or a remote key/value store.
package viper
import (
"log/slog"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCopyAndInsensitiviseMap(t *testing.T) {
var (
given = map[string]any{
"Foo": 32,
"Bar": map[any]any{
"ABc": "A",
"cDE": "B",
},
}
expected = map[string]any{
"foo": 32,
"bar": map[string]any{
"abc": "A",
"cde": "B",
},
}
)
got := copyAndInsensitiviseMap(given)
assert.Equal(t, expected, got)
_, ok := given["foo"]
assert.False(t, ok)
_, ok = given["bar"]
assert.False(t, ok)
m := given["Bar"].(map[any]any)
_, ok = m["ABc"]
assert.True(t, ok)
}
func TestAbsPathify(t *testing.T) {
skipWindows(t)
home := userHomeDir()
homer := filepath.Join(home, "homer")
wd, _ := os.Getwd()
t.Setenv("HOMER_ABSOLUTE_PATH", homer)
t.Setenv("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(slog.Default(), test.input)
assert.Equal(t, test.output, got)
}
}
================================================
FILE: viper.go
================================================
// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Viper is an application configuration system.
// It believes that applications can be configured a variety of ways
// via flags, ENVIRONMENT variables, configuration files retrieved
// from the file system, or a remote key/value store.
// Each item takes precedence over the item below it:
// overrides
// flag
// env
// config
// key/value store
// default
package viper
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"io"
fs "io/fs"
"log/slog"
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
"github.com/go-viper/mapstructure/v2"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/spf13/pflag"
"github.com/spf13/viper/internal/features"
)
var v *Viper
func init() {
v = New()
}
// A DecoderConfigOption can be passed to viper.Unmarshal to configure
// mapstructure.DecoderConfig options.
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(","),
// )
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
return func(c *mapstructure.DecoderConfig) {
c.DecodeHook = hook
}
}
// Viper is a prioritized configuration registry. It
// maintains a set of configuration sources, fetches
// values to populate those, and provides them according
// to the source's priority.
// The priority of the sources is the following:
// 1. overrides
// 2. flags
// 3. env. variables
// 4. config file
// 5. key/value store
// 6. defaults
//
// 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"
// }
//
// The resulting config will have the following values:
//
// {
// "secret": "somesecretkey",
// "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
keyDelim string
// A set of paths to look for the config file in
configPaths []string
// The filesystem to read config from.
fs afero.Fs
finder Finder
// A set of remote providers to search for the configuration
remoteProviders []*defaultRemoteProvider
// Name of file to look for inside the path
configName string
configFile string
configType string
configPermissions os.FileMode
envPrefix string
automaticEnvApplied bool
envKeyReplacer StringReplacer
allowEmptyEnv bool
parents []string
config map[string]any
override map[string]any
defaults map[string]any
kvstore map[string]any
pflags map[string]FlagValue
env map[string][]string
aliases map[string]string
typeByDefValue bool
onConfigChange func(fsnotify.Event)
logger *slog.Logger
encoderRegistry EncoderRegistry
decoderRegistry DecoderRegistry
decodeHook mapstructure.DecodeHookFunc
experimentalFinder bool
experimentalBindStruct bool
}
// New returns an initialized Viper instance.
func New() *Viper {
v := new(Viper)
v.keyDelim = "."
v.configName = "config"
v.configPermissions = os.FileMode(0o644)
v.fs = afero.NewOsFs()
v.config = make(map[string]any)
v.parents = []string{}
v.override = make(map[string]any)
v.defaults = make(map[string]any)
v.kvstore = make(map[string]any)
v.pflags = make(map[string]FlagValue)
v.env = make(map[string][]string)
v.aliases = make(map[string]string)
v.typeByDefValue = false
v.logger = slog.New(&discardHandler{})
codecRegistry := NewCodecRegistry()
v.encoderRegistry = codecRegistry
v.decoderRegistry = codecRegistry
v.experimentalFinder = features.Finder
v.experimentalBindStruct = features.BindStruct
return v
}
// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney.
// If you're unfamiliar with this style,
// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and
// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis.
type Option interface {
apply(v *Viper)
}
type optionFunc func(v *Viper)
func (fn optionFunc) apply(v *Viper) {
fn(v)
}
// KeyDelimiter sets the delimiter used for determining key parts.
// By default it's value is ".".
func KeyDelimiter(d string) Option {
return optionFunc(func(v *Viper) {
v.keyDelim = d
})
}
// StringReplacer applies a set of replacements to a string.
type StringReplacer interface {
// Replace returns a copy of s with all replacements performed.
Replace(s string) string
}
// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys.
func EnvKeyReplacer(r StringReplacer) Option {
return optionFunc(func(v *Viper) {
if r == nil {
return
}
v.envKeyReplacer = r
})
}
// WithDecodeHook sets a default decode hook for mapstructure.
func WithDecodeHook(h mapstructure.DecodeHookFunc) Option {
return optionFunc(func(v *Viper) {
if h == nil {
return
}
v.decodeHook = h
})
}
// NewWithOptions creates a new Viper instance.
func NewWithOptions(opts ...Option) *Viper {
v := New()
for _, opt := range opts {
opt.apply(v)
}
return v
}
// SetOptions sets the options on the global Viper instance.
//
// Be careful when using this function: subsequent calls may override options you set.
// It's always better to use a local Viper instance.
func SetOptions(opts ...Option) {
for _, opt := range opts {
opt.apply(v)
}
}
// Reset is intended for testing, will reset all to default settings.
// In the public interface for the viper package so applications
// can use it in their testing as well.
func Reset() {
v = New()
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
resetRemote()
}
// SupportedExts are universally supported extensions.
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
// 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()
if err != nil {
v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err))
os.Exit(1)
}
defer watcher.Close()
// we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way
filename, err := v.getConfigFile()
if err != nil {
v.logger.Error(fmt.Sprintf("get config file: %s", err))
initWG.Done()
return
}
configFile := filepath.Clean(filename)
configDir, _ := filepath.Split(configFile)
realConfigFile, _ := filepath.EvalSymlinks(filename)
eventsWG := sync.WaitGroup{}
eventsWG.Add(1)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok { // 'Events' channel is closed
eventsWG.Done()
return
}
currentConfigFile, _ := filepath.EvalSymlinks(filename)
// 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)
if (filepath.Clean(event.Name) == configFile &&
(event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||
(currentConfigFile != "" && currentConfigFile != realConfigFile) {
realConfigFile = currentConfigFile
err := v.ReadInConfig()
if err != nil {
v.logger.Error(fmt.Sprintf("read config file: %s", err))
}
if v.onConfigChange != nil {
v.onConfigChange(event)
}
} else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
eventsWG.Done()
return
}
case err, ok := <-watcher.Errors:
if ok { // 'Errors' channel is not closed
v.logger.Error(fmt.Sprintf("watcher error: %s", err))
}
eventsWG.Done()
return
}
}
}()
err = watcher.Add(configDir)
if err != nil {
v.logger.Error(fmt.Sprintf("failed to add watcher: %s", err))
initWG.Done()
return
}
initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on...
eventsWG.Wait() // now, wait for event loop to end in this go-routine...
}()
initWG.Wait() // make sure that the go routine above fully ended before returning
}
// 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) }
// 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 (v *Viper) SetConfigFile(in string) {
if in != "" {
v.configFile = in
}
}
// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// 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) }
// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use.
// E.g. if your prefix is "spf", the env registry will look for env
// variables that start with "SPF_".
func (v *Viper) SetEnvPrefix(in string) {
if in != "" {
v.envPrefix = in
}
}
// GetEnvPrefix returns the environment variable prefix.
func GetEnvPrefix() string { return v.GetEnvPrefix() }
// GetEnvPrefix returns the environment variable prefix.
func (v *Viper) GetEnvPrefix() string {
return v.envPrefix
}
func (v *Viper) mergeWithEnvPrefix(in string) string {
if v.envPrefix != "" {
return strings.ToUpper(v.envPrefix + "_" + in)
}
return strings.ToUpper(in)
}
// AllowEmptyEnv tells Viper to consider set,
// 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) }
// AllowEmptyEnv tells Viper to consider set,
// but empty environment variables as valid values instead of falling back.
// For backward compatibility reasons this is false by default.
func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
v.allowEmptyEnv = allowEmptyEnv
}
// TODO: should getEnv logic be moved into find(). Can generalize the use of
// rewriting keys many things, Ex: Get('someKey') -> some_key
// (camel case to snake case for JSON keys perhaps)
// getEnv is a wrapper around os.Getenv which replaces characters in the original
// key. This allows env vars which have different keys than the config object
// keys.
func (v *Viper) getEnv(key string) (string, bool) {
if v.envKeyReplacer != nil {
key = v.envKeyReplacer.Replace(key)
}
val, ok := os.LookupEnv(key)
return val, ok && (v.allowEmptyEnv || val != "")
}
// ConfigFileUsed returns the file used to populate the config registry.
func ConfigFileUsed() string { return v.ConfigFileUsed() }
// ConfigFileUsed returns the file used to populate the config registry.
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) }
// AddConfigPath adds a path for Viper to search for the config file in.
// Can be called multiple times to define multiple search paths.
func (v *Viper) AddConfigPath(in string) {
if v.finder != nil {
v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "AddConfigPath"))
}
if in != "" {
absin := absPathify(v.logger, in)
v.logger.Info("adding path to search paths", "path", absin)
if !slices.Contains(v.configPaths, absin) {
v.configPaths = append(v.configPaths, absin)
}
}
}
// searchMap recursively searches for a value for path in source map.
// 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]any, path []string) any {
if len(path) == 0 {
return source
}
next, ok := source[path[0]]
if ok {
// Fast path
if len(path) == 1 {
return next
}
// Nested case
switch next := next.(type) {
case map[any]any:
return v.searchMap(cast.ToStringMap(next), path[1:])
case map[string]any:
// Type assertion is safe here since it is only reached
// if the type of `next` is the same as the type being asserted
return v.searchMap(next, path[1:])
default:
// got a value but nested key expected, return "nil" for not found
return nil
}
}
return nil
}
// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice.
//
// 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"].
//
// This should be useful only at config level (other maps may not contain dots
// in their keys).
//
// Note: This assumes that the path entries and map keys are lower cased.
func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any {
if len(path) == 0 {
return source
}
// search for path prefixes, starting from the longest one
for i := len(path); i > 0; i-- {
prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim))
var val any
switch sourceIndexable := source.(type) {
case []any:
val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path)
case map[string]any:
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 []any,
prefixKey string,
pathIndex int,
path []string,
) any {
// 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[any]any:
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
case map[string]any, []any:
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]any,
prefixKey string,
pathIndex int,
path []string,
) any {
next, ok := sourceMap[prefixKey]
if !ok {
return nil
}
// Fast path
if pathIndex == len(path) {
return next
}
// Nested case
switch n := next.(type) {
case map[any]any:
return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:])
case map[string]any, []any:
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
}
// 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
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string {
var parentVal any
for i := 1; i < len(path); i++ {
parentVal = v.searchMap(m, path[0:i])
if parentVal == nil {
// not found, no need to add more path elements
return ""
}
switch parentVal.(type) {
case map[any]any:
continue
case map[string]any:
continue
default:
// parentVal is a regular value which shadows "path"
return strings.Join(path[0:i], v.keyDelim)
}
}
return ""
}
// 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
func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
// unify input map
var m map[string]interface{}
switch miv := mi.(type) {
case map[string]string:
m = castMapStringToMapInterface(miv)
case map[string]FlagValue:
m = castMapFlagToMapInterface(miv)
default:
return ""
}
// scan paths
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
if _, ok := m[parentKey]; ok {
return parentKey
}
}
return ""
}
// 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
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
for i := 1; i < len(path); i++ {
parentKey = strings.Join(path[0:i], v.keyDelim)
if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok {
return parentKey
}
}
return ""
}
// SetTypeByDefaultValue enables or disables the inference of a key value's
// type when the Get function is used based upon a key's default value as
// opposed to the value returned based on the normal fetch logic.
//
// For example, if a key has a default value of []string{} and the same key
// is set via an environment variable to "a b c", a call to the Get function
// 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"}
//
// Otherwise the Get function would return:
//
// "a b c"
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
// SetTypeByDefaultValue enables or disables the inference of a key value's
// type when the Get function is used based upon a key's default value as
// opposed to the value returned based on the normal fetch logic.
//
// For example, if a key has a default value of []string{} and the same key
// is set via an environment variable to "a b c", a call to the Get function
// 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"}
//
// Otherwise the Get function would return:
//
// "a b c"
func (v *Viper) SetTypeByDefaultValue(enable bool) {
v.typeByDefValue = enable
}
// GetViper gets the global Viper instance.
func GetViper() *Viper {
return v
}
// Get can retrieve any value given the key to use.
// Get is case-insensitive for a key.
// Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func Get(key string) any { return v.Get(key) }
// Get retrieves the value associated with the key.
// Get is case-insensitive for a key.
// Get has the behavior of returning the value associated with the first
// place from where it is set. Viper will check in the following order:
// override, flag, env, config file, key/value store, default
//
// Get returns an interface. For a specific value use one of the Get____ methods.
func (v *Viper) Get(key string) any {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, true)
if val == nil {
return nil
}
if v.typeByDefValue {
// TODO(bep) this branch isn't covered by a single test.
valType := val
path := strings.Split(lcaseKey, v.keyDelim)
defVal := v.searchMap(v.defaults, path)
if defVal != nil {
valType = defVal
}
switch valType.(type) {
case bool:
return cast.ToBool(val)
case string:
return cast.ToString(val)
case int32, int16, int8, int:
return cast.ToInt(val)
case uint:
return cast.ToUint(val)
case uint32:
return cast.ToUint32(val)
case uint64:
return cast.ToUint64(val)
case int64:
return cast.ToInt64(val)
case float64, float32:
return cast.ToFloat64(val)
case time.Time:
return cast.ToTime(val)
case time.Duration:
return cast.ToDuration(val)
case []string:
return cast.ToStringSlice(val)
case []int:
return cast.ToIntSlice(val)
case []time.Duration:
return cast.ToDurationSlice(val)
}
}
return val
}
// 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) }
// Sub returns a new Viper instance representing a sub tree of this instance.
// Sub is case-insensitive for a key.
func (v *Viper) Sub(key string) *Viper {
subv := New()
data := v.Get(key)
if data == nil {
return nil
}
if reflect.TypeOf(data).Kind() == reflect.Map {
subv.parents = append([]string(nil), v.parents...)
subv.parents = append(subv.parents, strings.ToLower(key))
subv.automaticEnvApplied = v.automaticEnvApplied
subv.envPrefix = v.envPrefix
subv.envKeyReplacer = v.envKeyReplacer
subv.keyDelim = v.keyDelim
subv.config = cast.ToStringMap(data)
return subv
}
return nil
}
// GetString returns the value associated with the key as a string.
func GetString(key string) string { return v.GetString(key) }
// GetString returns the value associated with the key as a string.
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) }
// GetBool returns the value associated with the key as a boolean.
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) }
// GetInt returns the value associated with the key as an integer.
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) }
// GetInt32 returns the value associated with the key as an integer.
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) }
// GetInt64 returns the value associated with the key as an integer.
func (v *Viper) GetInt64(key string) int64 {
return cast.ToInt64(v.Get(key))
}
// GetUint8 returns the value associated with the key as an unsigned integer.
func GetUint8(key string) uint8 { return v.GetUint8(key) }
// GetUint8 returns the value associated with the key as an unsigned integer.
func (v *Viper) GetUint8(key string) uint8 {
return cast.ToUint8(v.Get(key))
}
// GetUint returns the value associated with the key as an unsigned integer.
func GetUint(key string) uint { return v.GetUint(key) }
// GetUint returns the value associated with the key as an unsigned integer.
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) }
// GetUint16 returns the value associated with the key as an unsigned integer.
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) }
// GetUint32 returns the value associated with the key as an unsigned integer.
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) }
// GetUint64 returns the value associated with the key as an unsigned integer.
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) }
// GetFloat64 returns the value associated with the key as a float64.
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) }
// GetTime returns the value associated with the key as time.
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) }
// GetDuration returns the value associated with the key as a duration.
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) }
// GetIntSlice returns the value associated with the key as a slice of int values.
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) }
// GetStringSlice returns the value associated with the key as a slice of strings.
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]any { return v.GetStringMap(key) }
// GetStringMap returns the value associated with the key as a map of interfaces.
func (v *Viper) GetStringMap(key string) map[string]any {
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) }
// GetStringMapString returns the value associated with the key as a map of strings.
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) }
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
return cast.ToStringMapStringSlice(v.Get(key))
}
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
// GetSizeInBytes returns the size of the value associated with the given key
// in bytes.
func (v *Viper) GetSizeInBytes(key string) uint {
sizeStr := cast.ToString(v.Get(key))
return parseSizeInBytes(sizeStr)
}
// UnmarshalKey takes a single key and unmarshals it into a Struct.
func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
return v.UnmarshalKey(key, rawVal, opts...)
}
// UnmarshalKey takes a single key and unmarshals it into a Struct.
func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error {
return decode(v.Get(key), v.defaultDecoderConfig(rawVal, opts...))
}
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
return v.Unmarshal(rawVal, opts...)
}
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
// on the fields of the structure are properly set.
func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
keys := v.AllKeys()
if v.experimentalBindStruct {
// TODO: make this optional?
structKeys, err := v.decodeStructKeys(rawVal, opts...)
if err != nil {
return err
}
keys = append(keys, structKeys...)
}
// TODO: struct keys should be enough?
return decode(v.getSettings(keys), v.defaultDecoderConfig(rawVal, opts...))
}
func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) {
var structKeyMap map[string]any
err := decode(input, v.defaultDecoderConfig(&structKeyMap, opts...))
if err != nil {
return nil, err
}
flattenedStructKeyMap := v.flattenAndMergeMap(map[string]bool{}, structKeyMap, "")
r := make([]string, 0, len(flattenedStructKeyMap))
for v := range flattenedStructKeyMap {
r = append(r, v)
}
return r, nil
}
// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
// of time.Duration values & string slices.
func (v *Viper) defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
decodeHook := v.decodeHook
if decodeHook == nil {
decodeHook = mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
// mapstructure.StringToSliceHookFunc(","),
stringToWeakSliceHookFunc(","),
)
}
c := &mapstructure.DecoderConfig{
Metadata: nil,
WeaklyTypedInput: true,
DecodeHook: decodeHook,
}
for _, opt := range opts {
opt(c)
}
// Do not allow overwriting the output
c.Result = output
return c
}
// As of mapstructure v2.0.0 StringToSliceHookFunc checks if the return type is a string slice.
// This function removes that check.
// TODO: implement a function that checks if the value can be converted to the return type and use it instead.
func stringToWeakSliceHookFunc(sep string) mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.
func decode(input any, config *mapstructure.DecoderConfig) error {
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
return v.UnmarshalExact(rawVal, opts...)
}
// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent
// in the destination struct.
func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
config := v.defaultDecoderConfig(rawVal, opts...)
config.ErrorUnused = true
keys := v.AllKeys()
if v.experimentalBindStruct {
// TODO: make this optional?
structKeys, err := v.decodeStructKeys(rawVal, opts...)
if err != nil {
return err
}
keys = append(keys, structKeys...)
}
// TODO: struct keys should be enough?
return decode(v.getSettings(keys), 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) }
// BindPFlags binds a full flag set to the configuration, using each flag's long
// name as the config key.
func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
return v.BindFlagValues(pflagValueSet{flags})
}
// 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"))
func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }
// 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"))
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) }
// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long
// name as the config key.
func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
flags.VisitAll(func(flag FlagValue) {
if err = v.BindFlagValue(flag.Name(), flag); err != nil {
return
}
})
return nil
}
// BindFlagValue binds a specific key to a FlagValue.
func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) }
// BindFlagValue binds a specific key to a FlagValue.
func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
if flag == nil {
return fmt.Errorf("flag for %q is nil", key)
}
v.pflags[strings.ToLower(key)] = flag
return nil
}
// 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...) }
// 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 (v *Viper) BindEnv(input ...string) error {
if len(input) == 0 {
return fmt.Errorf("missing key to bind to")
}
key := strings.ToLower(input[0])
if len(input) == 1 {
v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key))
} else {
v.env[key] = append(v.env[key], input[1:]...)
}
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...) }
// MustBindEnv wraps BindEnv in a panic.
// If there is an error binding an environment variable, MustBindEnv will
// panic.
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.
// Viper will then check in the following order:
// flag, env, config file, key/value store.
// Lastly, if no value was found and flagDefault is true, and if the key
// corresponds to a flag, the flag's default value is returned.
//
// Note: this assumes a lower-cased key given.
func (v *Viper) find(lcaseKey string, flagDefault bool) any {
var (
val any
exists bool
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1
)
// compute the path through the nested maps to the nested value
if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {
return nil
}
// if the requested key is an alias, then return the proper key
lcaseKey = v.realKey(lcaseKey)
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1
// Set() override first
val = v.searchMap(v.override, path)
if val != nil {
return val
}
if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {
return nil
}
// PFlag override next
flag, exists := v.pflags[lcaseKey]
if exists && flag.HasChanged() {
switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64":
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
case "stringSlice", "stringArray":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
case "boolSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToBoolSlice(res)
case "intSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
case "uintSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToUintSlice(res)
case "float64Slice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToFloat64Slice(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())
case "stringToInt":
return stringToIntConv(flag.ValueString())
default:
return flag.ValueString()
}
}
if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {
return nil
}
// 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(envKey)); ok {
return val
}
if nested && v.isPathShadowedInAutoEnv(path) != "" {
return nil
}
}
envkeys, exists := v.env[lcaseKey]
if exists {
for _, envkey := range envkeys {
if val, ok := v.getEnv(envkey); ok {
return val
}
}
}
if nested && v.isPathShadowedInFlatMap(path, v.env) != "" {
return nil
}
// Config file next
val = v.searchIndexableWithPathPrefixes(v.config, path)
if val != nil {
return val
}
if nested && v.isPathShadowedInDeepMap(path, v.config) != "" {
return nil
}
// K/V store next
val = v.searchMap(v.kvstore, path)
if val != nil {
return val
}
if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" {
return nil
}
// Default next
val = v.searchMap(v.defaults, path)
if val != nil {
return val
}
if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" {
return nil
}
if flagDefault {
// last chance: if no value is found and a flag does exist for the key,
// get the flag's default value even if the flag's value has not been set.
if flag, exists := v.pflags[lcaseKey]; exists {
switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64":
return cast.ToInt(flag.ValueString())
case "bool":
return cast.ToBool(flag.ValueString())
case "stringSlice", "stringArray":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return res
case "boolSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToBoolSlice(res)
case "intSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToIntSlice(res)
case "uintSlice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToUintSlice(res)
case "float64Slice":
s := strings.TrimPrefix(flag.ValueString(), "[")
s = strings.TrimSuffix(s, "]")
res, _ := readAsCSV(s)
return cast.ToFloat64Slice(res)
case "stringToString":
return stringToStringConv(flag.ValueString())
case "stringToInt":
return stringToIntConv(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()
}
}
// last item, no need to check shadowing
}
return nil
}
func readAsCSV(val string) ([]string, error) {
if val == "" {
return []string{}, nil
}
stringReader := strings.NewReader(val)
csvReader := csv.NewReader(stringReader)
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]any is returned in order to enable cast.ToStringMap.
func stringToStringConv(val string) any {
val = strings.Trim(val, "[]")
// An empty string would cause an empty map
if val == "" {
return map[string]any{}
}
r := csv.NewReader(strings.NewReader(val))
ss, err := r.Read()
if err != nil {
return nil
}
out := make(map[string]any, len(ss))
for _, pair := range ss {
k, vv, found := strings.Cut(pair, "=")
if !found {
return nil
}
out[k] = vv
}
return out
}
// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/string_to_int.go#L68
// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap.
func stringToIntConv(val string) any {
val = strings.Trim(val, "[]")
// An empty string would cause an empty map
if val == "" {
return map[string]any{}
}
ss := strings.Split(val, ",")
out := make(map[string]any, len(ss))
for _, pair := range ss {
k, vv, found := strings.Cut(pair, "=")
if !found {
return nil
}
var err error
out[k], err = strconv.Atoi(vv)
if err != nil {
return nil
}
}
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) }
// IsSet checks to see if the key has been set in any of the data locations.
// IsSet is case-insensitive for a key.
func (v *Viper) IsSet(key string) bool {
lcaseKey := strings.ToLower(key)
val := v.find(lcaseKey, false)
return val != nil
}
// 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() }
// 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 (v *Viper) AutomaticEnv() {
v.automaticEnvApplied = true
}
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
// SetEnvKeyReplacer sets the strings.Replacer on the viper object
// Useful for mapping an environmental variable to a key that does
// not match it.
func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
v.envKeyReplacer = r
}
// 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, key string) { v.RegisterAlias(alias, key) }
// RegisterAlias creates an alias that provides another accessor for the same key.
// This enables one to change a name without breaking the application.
func (v *Viper) RegisterAlias(alias, key string) {
v.registerAlias(alias, strings.ToLower(key))
}
func (v *Viper) registerAlias(alias, key string) {
alias = strings.ToLower(alias)
gitextract_5z5ozi0n/ ├── .editorconfig ├── .envrc ├── .github/ │ ├── .editorconfig │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yaml │ │ ├── config.yml │ │ └── feature_request.yaml │ ├── PULL_REQUEST_TEMPLATES.md │ ├── dependabot.yaml │ ├── octoslash/ │ │ ├── policies/ │ │ │ ├── collaborator.cedar │ │ │ └── triager.cedar │ │ └── principals.json │ ├── release.yml │ └── workflows/ │ ├── checks.yaml │ ├── ci.yaml │ ├── octoslash.yaml │ └── stale.yaml ├── .gitignore ├── .golangci.yaml ├── .yamlignore ├── .yamllint.yaml ├── LICENSE ├── Makefile ├── README.md ├── TROUBLESHOOTING.md ├── UPGRADE.md ├── encoding.go ├── encoding_test.go ├── errors.go ├── experimental.go ├── file.go ├── finder.go ├── finder_example_test.go ├── finder_test.go ├── flags.go ├── flags_test.go ├── flake.nix ├── go.mod ├── go.sum ├── internal/ │ ├── encoding/ │ │ ├── dotenv/ │ │ │ ├── codec.go │ │ │ ├── codec_test.go │ │ │ └── map_utils.go │ │ ├── json/ │ │ │ ├── codec.go │ │ │ └── codec_test.go │ │ ├── toml/ │ │ │ ├── codec.go │ │ │ └── codec_test.go │ │ └── yaml/ │ │ ├── codec.go │ │ └── codec_test.go │ ├── features/ │ │ ├── bind_struct.go │ │ ├── bind_struct_default.go │ │ ├── finder.go │ │ └── finder_default.go │ └── testutil/ │ └── filepath.go ├── logger.go ├── overrides_test.go ├── remote/ │ ├── go.mod │ ├── go.sum │ └── remote.go ├── remote.go ├── util.go ├── util_test.go ├── viper.go ├── viper_test.go └── viper_yaml_test.go
SYMBOL INDEX (436 symbols across 32 files)
FILE: encoding.go
type Encoder (line 16) | type Encoder interface
type Decoder (line 22) | type Decoder interface
type Codec (line 27) | type Codec interface
type EncoderRegistry (line 39) | type EncoderRegistry interface
type DecoderRegistry (line 48) | type DecoderRegistry interface
type CodecRegistry (line 53) | type CodecRegistry interface
function WithEncoderRegistry (line 59) | func WithEncoderRegistry(r EncoderRegistry) Option {
function WithDecoderRegistry (line 70) | func WithDecoderRegistry(r DecoderRegistry) Option {
function WithCodecRegistry (line 81) | func WithCodecRegistry(r CodecRegistry) Option {
type DefaultCodecRegistry (line 93) | type DefaultCodecRegistry struct
method init (line 109) | func (r *DefaultCodecRegistry) init() {
method RegisterCodec (line 118) | func (r *DefaultCodecRegistry) RegisterCodec(format string, codec Code...
method Encoder (line 132) | func (r *DefaultCodecRegistry) Encoder(format string) (Encoder, error) {
method Decoder (line 144) | func (r *DefaultCodecRegistry) Decoder(format string) (Decoder, error) {
method codec (line 153) | func (r *DefaultCodecRegistry) codec(format string) (Codec, bool) {
function NewCodecRegistry (line 101) | func NewCodecRegistry() *DefaultCodecRegistry {
FILE: encoding_test.go
type codec (line 10) | type codec struct
method Encode (line 12) | func (codec) Encode(_ map[string]any) ([]byte, error) {
method Decode (line 16) | func (codec) Decode(_ []byte, _ map[string]any) error {
function TestDefaultCodecRegistry (line 20) | func TestDefaultCodecRegistry(t *testing.T) {
FILE: errors.go
type FileLookupError (line 11) | type FileLookupError interface
type ConfigFileNotFoundError (line 20) | type ConfigFileNotFoundError struct
method Error (line 26) | func (e ConfigFileNotFoundError) Error() string {
method Unwrap (line 31) | func (e ConfigFileNotFoundError) Unwrap() error {
type FileNotFoundFromSearchError (line 37) | type FileNotFoundFromSearchError struct
method fileLookup (line 42) | func (e FileNotFoundFromSearchError) fileLookup() {}
method Error (line 45) | func (e FileNotFoundFromSearchError) Error() string {
type FileNotFoundError (line 56) | type FileNotFoundError struct
method fileLookup (line 61) | func (e FileNotFoundError) fileLookup() {}
method Error (line 64) | func (e FileNotFoundError) Error() string {
type ConfigFileAlreadyExistsError (line 69) | type ConfigFileAlreadyExistsError
method Error (line 72) | func (e ConfigFileAlreadyExistsError) Error() string {
type ConfigMarshalError (line 77) | type ConfigMarshalError struct
method Error (line 82) | func (e ConfigMarshalError) Error() string {
type UnsupportedConfigError (line 88) | type UnsupportedConfigError
method Error (line 91) | func (str UnsupportedConfigError) Error() string {
FILE: experimental.go
function ExperimentalBindStruct (line 4) | func ExperimentalBindStruct() Option {
FILE: file.go
function ExperimentalFinder (line 12) | func ExperimentalFinder() Option {
method findConfigFile (line 19) | func (v *Viper) findConfigFile() (string, error) {
method findConfigFileWithFinder (line 45) | func (v *Viper) findConfigFileWithFinder(finder Finder) (string, error) {
method findConfigFileOld (line 62) | func (v *Viper) findConfigFileOld() (string, error) {
method searchInPath (line 74) | func (v *Viper) searchInPath(in string) (filename string) {
function exists (line 94) | func exists(fs afero.Fs, path string) (bool, error) {
FILE: finder.go
function WithFinder (line 10) | func WithFinder(f Finder) Option {
type Finder (line 21) | type Finder interface
function Finders (line 26) | func Finders(finders ...Finder) Finder {
type combinedFinder (line 31) | type combinedFinder struct
method Find (line 36) | func (c *combinedFinder) Find(fsys afero.Fs) ([]string, error) {
FILE: finder_example_test.go
function ExampleFinder (line 12) | func ExampleFinder() {
function ExampleFinders (line 40) | func ExampleFinders() {
FILE: finder_test.go
type finderStub (line 11) | type finderStub struct
method Find (line 15) | func (f finderStub) Find(_ afero.Fs) ([]string, error) {
function TestFinders (line 19) | func TestFinders(t *testing.T) {
FILE: flags.go
type FlagValueSet (line 7) | type FlagValueSet interface
type FlagValue (line 13) | type FlagValue interface
type pflagValueSet (line 22) | type pflagValueSet struct
method VisitAll (line 27) | func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) {
type pflagValue (line 35) | type pflagValue struct
method HasChanged (line 40) | func (p pflagValue) HasChanged() bool {
method Name (line 45) | func (p pflagValue) Name() string {
method ValueString (line 50) | func (p pflagValue) ValueString() string {
method ValueType (line 55) | func (p pflagValue) ValueType() string {
FILE: flags_test.go
function TestBindFlagValueSet (line 11) | func TestBindFlagValueSet(t *testing.T) {
function TestBindFlagValue (line 46) | func TestBindFlagValue(t *testing.T) {
FILE: internal/encoding/dotenv/codec.go
constant keyDelimiter (line 12) | keyDelimiter = "_"
type Codec (line 16) | type Codec struct
method Encode (line 19) | func (Codec) Encode(v map[string]any) ([]byte, error) {
method Decode (line 45) | func (Codec) Decode(b []byte, v map[string]any) error {
FILE: internal/encoding/dotenv/codec_test.go
constant original (line 11) | original = `# key-value pair
constant encoded (line 16) | encoded = `KEY=value
function TestCodec_Encode (line 24) | func TestCodec_Encode(t *testing.T) {
function TestCodec_Decode (line 33) | func TestCodec_Decode(t *testing.T) {
FILE: internal/encoding/dotenv/map_utils.go
function flattenAndMergeMap (line 12) | func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter stri...
FILE: internal/encoding/json/codec.go
type Codec (line 8) | type Codec struct
method Encode (line 11) | func (Codec) Encode(v map[string]any) ([]byte, error) {
method Decode (line 17) | func (Codec) Decode(b []byte, v map[string]any) error {
FILE: internal/encoding/json/codec_test.go
constant encoded (line 11) | encoded = `{
function TestCodec_Encode (line 56) | func TestCodec_Encode(t *testing.T) {
function TestCodec_Decode (line 65) | func TestCodec_Decode(t *testing.T) {
FILE: internal/encoding/toml/codec.go
type Codec (line 8) | type Codec struct
method Encode (line 11) | func (Codec) Encode(v map[string]any) ([]byte, error) {
method Decode (line 16) | func (Codec) Decode(b []byte, v map[string]any) error {
FILE: internal/encoding/toml/codec_test.go
constant original (line 11) | original = `# key-value pair
constant encoded (line 31) | encoded = `key = 'value'
function TestCodec_Encode (line 66) | func TestCodec_Encode(t *testing.T) {
function TestCodec_Decode (line 75) | func TestCodec_Decode(t *testing.T) {
FILE: internal/encoding/yaml/codec.go
type Codec (line 6) | type Codec struct
method Encode (line 9) | func (Codec) Encode(v map[string]any) ([]byte, error) {
method Decode (line 14) | func (Codec) Decode(b []byte, v map[string]any) error {
FILE: internal/encoding/yaml/codec_test.go
constant original (line 11) | original = `# key-value pair
constant encoded (line 32) | encoded = `key: value
function TestCodec_Encode (line 97) | func TestCodec_Encode(t *testing.T) {
function TestCodec_Decode (line 106) | func TestCodec_Decode(t *testing.T) {
FILE: internal/features/bind_struct.go
constant BindStruct (line 6) | BindStruct = true
FILE: internal/features/bind_struct_default.go
constant BindStruct (line 6) | BindStruct = false
FILE: internal/features/finder.go
constant Finder (line 6) | Finder = true
FILE: internal/features/finder_default.go
constant Finder (line 6) | Finder = false
FILE: internal/testutil/filepath.go
function AbsFilePath (line 9) | func AbsFilePath(t *testing.T, path string) string {
FILE: logger.go
function WithLogger (line 9) | func WithLogger(l *slog.Logger) Option {
type discardHandler (line 15) | type discardHandler struct
method Enabled (line 17) | func (n *discardHandler) Enabled(_ context.Context, _ slog.Level) bool {
method Handle (line 21) | func (n *discardHandler) Handle(_ context.Context, _ slog.Record) error {
method WithAttrs (line 25) | func (n *discardHandler) WithAttrs(_ []slog.Attr) slog.Handler {
method WithGroup (line 29) | func (n *discardHandler) WithGroup(_ string) slog.Handler {
FILE: overrides_test.go
type layer (line 11) | type layer
constant defaultLayer (line 14) | defaultLayer layer = iota + 1
constant overrideLayer (line 15) | overrideLayer
function TestNestedOverrides (line 18) | func TestNestedOverrides(t *testing.T) {
function overrideDefault (line 77) | func overrideDefault(assert *assert.Assertions, firstPath string, firstV...
function override (line 81) | func override(assert *assert.Assertions, firstPath string, firstValue an...
function overrideFromLayer (line 96) | func overrideFromLayer(l layer, assert *assert.Assertions, firstPath str...
function deepCheckValue (line 130) | func deepCheckValue(assert *assert.Assertions, v *Viper, l layer, keys [...
FILE: remote.go
function resetRemote (line 14) | func resetRemote() {
type remoteConfigFactory (line 18) | type remoteConfigFactory interface
type RemoteResponse (line 25) | type RemoteResponse struct
type UnsupportedRemoteProviderError (line 35) | type UnsupportedRemoteProviderError
method Error (line 38) | func (str UnsupportedRemoteProviderError) Error() string {
type RemoteConfigError (line 44) | type RemoteConfigError
method Error (line 47) | func (rce RemoteConfigError) Error() string {
type defaultRemoteProvider (line 51) | type defaultRemoteProvider struct
method Provider (line 58) | func (rp defaultRemoteProvider) Provider() string {
method Endpoint (line 62) | func (rp defaultRemoteProvider) Endpoint() string {
method Path (line 66) | func (rp defaultRemoteProvider) Path() string {
method SecretKeyring (line 70) | func (rp defaultRemoteProvider) SecretKeyring() string {
type RemoteProvider (line 78) | type RemoteProvider interface
function AddRemoteProvider (line 93) | func AddRemoteProvider(provider, endpoint, path string) error {
method AddRemoteProvider (line 105) | func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
function AddSecureRemoteProvider (line 134) | func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring str...
method AddSecureRemoteProvider (line 148) | func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secret...
method providerPathExists (line 168) | func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
function ReadRemoteConfig (line 179) | func ReadRemoteConfig() error { return v.ReadRemoteConfig() }
method ReadRemoteConfig (line 183) | func (v *Viper) ReadRemoteConfig() error {
function WatchRemoteConfig (line 188) | func WatchRemoteConfig() error { return v.WatchRemoteConfig() }
method WatchRemoteConfig (line 191) | func (v *Viper) WatchRemoteConfig() error {
method WatchRemoteConfigOnChannel (line 196) | func (v *Viper) WatchRemoteConfigOnChannel() error {
method getKeyValueConfig (line 201) | func (v *Viper) getKeyValueConfig() error {
method getRemoteConfig (line 225) | func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]any...
method watchKeyValueConfigOnChannel (line 235) | func (v *Viper) watchKeyValueConfigOnChannel() error {
method watchKeyValueConfig (line 259) | func (v *Viper) watchKeyValueConfig() error {
method watchRemoteConfig (line 277) | func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]a...
FILE: remote/remote.go
type remoteConfigProvider (line 20) | type remoteConfigProvider struct
method Get (line 22) | func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader...
method Watch (line 34) | func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Read...
method WatchChannel (line 47) | func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (...
function getConfigManager (line 75) | func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, err...
function init (line 119) | func init() {
FILE: util.go
type ConfigParseError (line 26) | type ConfigParseError struct
method Error (line 31) | func (pe ConfigParseError) Error() string {
method Unwrap (line 36) | func (pe ConfigParseError) Unwrap() error {
function toCaseInsensitiveValue (line 42) | func toCaseInsensitiveValue(value any) any {
function copyAndInsensitiviseMap (line 55) | func copyAndInsensitiviseMap(m map[string]any) map[string]any {
function insensitiviseVal (line 73) | func insensitiviseVal(val any) any {
function insensitiviseMap (line 89) | func insensitiviseMap(m map[string]any) {
function insensitiveArray (line 102) | func insensitiveArray(a []any) {
function absPathify (line 108) | func absPathify(logger *slog.Logger, inPath string) string {
function userHomeDir (line 131) | func userHomeDir() string {
function safeMul (line 142) | func safeMul(a, b uint) uint {
function parseSizeInBytes (line 151) | func parseSizeInBytes(sizeStr string) uint {
function deepSearch (line 189) | func deepSearch(m map[string]any, path []string) map[string]any {
FILE: util_test.go
function TestCopyAndInsensitiviseMap (line 22) | func TestCopyAndInsensitiviseMap(t *testing.T) {
function TestAbsPathify (line 53) | func TestAbsPathify(t *testing.T) {
FILE: viper.go
function init (line 50) | func init() {
type DecoderConfigOption (line 56) | type DecoderConfigOption
function DecodeHook (line 65) | func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
type Viper (line 107) | type Viper struct
method OnConfigChange (line 274) | func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
method WatchConfig (line 282) | func (v *Viper) WatchConfig() {
method SetConfigFile (line 361) | func (v *Viper) SetConfigFile(in string) {
method SetEnvPrefix (line 375) | func (v *Viper) SetEnvPrefix(in string) {
method GetEnvPrefix (line 385) | func (v *Viper) GetEnvPrefix() string {
method mergeWithEnvPrefix (line 389) | func (v *Viper) mergeWithEnvPrefix(in string) string {
method AllowEmptyEnv (line 405) | func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) {
method getEnv (line 416) | func (v *Viper) getEnv(key string) (string, bool) {
method ConfigFileUsed (line 430) | func (v *Viper) ConfigFileUsed() string { return v.configFile }
method AddConfigPath (line 438) | func (v *Viper) AddConfigPath(in string) {
method searchMap (line 456) | func (v *Viper) searchMap(source map[string]any, path []string) any {
method searchIndexableWithPathPrefixes (line 495) | func (v *Viper) searchIndexableWithPathPrefixes(source any, path []str...
method searchSliceWithPathPrefixes (line 524) | func (v *Viper) searchSliceWithPathPrefixes(
method searchMapWithPathPrefixes (line 560) | func (v *Viper) searchMapWithPathPrefixes(
method isPathShadowedInDeepMap (line 595) | func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]an...
method isPathShadowedInFlatMap (line 621) | func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string {
method isPathShadowedInAutoEnv (line 649) | func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
method SetTypeByDefaultValue (line 690) | func (v *Viper) SetTypeByDefaultValue(enable bool) {
method Get (line 715) | func (v *Viper) Get(key string) any {
method Sub (line 770) | func (v *Viper) Sub(key string) *Viper {
method GetString (line 794) | func (v *Viper) GetString(key string) string {
method GetBool (line 802) | func (v *Viper) GetBool(key string) bool {
method GetInt (line 810) | func (v *Viper) GetInt(key string) int {
method GetInt32 (line 818) | func (v *Viper) GetInt32(key string) int32 {
method GetInt64 (line 826) | func (v *Viper) GetInt64(key string) int64 {
method GetUint8 (line 834) | func (v *Viper) GetUint8(key string) uint8 {
method GetUint (line 842) | func (v *Viper) GetUint(key string) uint {
method GetUint16 (line 850) | func (v *Viper) GetUint16(key string) uint16 {
method GetUint32 (line 858) | func (v *Viper) GetUint32(key string) uint32 {
method GetUint64 (line 866) | func (v *Viper) GetUint64(key string) uint64 {
method GetFloat64 (line 874) | func (v *Viper) GetFloat64(key string) float64 {
method GetTime (line 882) | func (v *Viper) GetTime(key string) time.Time {
method GetDuration (line 890) | func (v *Viper) GetDuration(key string) time.Duration {
method GetIntSlice (line 898) | func (v *Viper) GetIntSlice(key string) []int {
method GetStringSlice (line 906) | func (v *Viper) GetStringSlice(key string) []string {
method GetStringMap (line 914) | func (v *Viper) GetStringMap(key string) map[string]any {
method GetStringMapString (line 922) | func (v *Viper) GetStringMapString(key string) map[string]string {
method GetStringMapStringSlice (line 930) | func (v *Viper) GetStringMapStringSlice(key string) map[string][]string {
method GetSizeInBytes (line 940) | func (v *Viper) GetSizeInBytes(key string) uint {
method UnmarshalKey (line 951) | func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderCo...
method Unmarshal (line 963) | func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) err...
method decodeStructKeys (line 980) | func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOptio...
method defaultDecoderConfig (line 1000) | func (v *Viper) defaultDecoderConfig(output any, opts ...DecoderConfig...
method UnmarshalExact (line 1065) | func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption...
method BindPFlags (line 1091) | func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
method BindPFlag (line 1107) | func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {
method BindFlagValues (line 1120) | func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) {
method BindFlagValue (line 1133) | func (v *Viper) BindFlagValue(key string, flag FlagValue) error {
method BindEnv (line 1155) | func (v *Viper) BindEnv(input ...string) error {
method MustBindEnv (line 1179) | func (v *Viper) MustBindEnv(input ...string) {
method find (line 1194) | func (v *Viper) find(lcaseKey string, flagDefault bool) any {
method IsSet (line 1438) | func (v *Viper) IsSet(key string) bool {
method AutomaticEnv (line 1450) | func (v *Viper) AutomaticEnv() {
method SetEnvKeyReplacer (line 1462) | func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) {
method RegisterAlias (line 1472) | func (v *Viper) RegisterAlias(alias, key string) {
method registerAlias (line 1476) | func (v *Viper) registerAlias(alias, key string) {
method realKey (line 1508) | func (v *Viper) realKey(key string) string {
method InConfig (line 1522) | func (v *Viper) InConfig(key string) bool {
method SetDefault (line 1540) | func (v *Viper) SetDefault(key string, value any) {
method Set (line 1563) | func (v *Viper) Set(key string, value any) {
method ReadInConfig (line 1582) | func (v *Viper) ReadInConfig() error {
method MergeInConfig (line 1619) | func (v *Viper) MergeInConfig() error {
method ReadConfig (line 1644) | func (v *Viper) ReadConfig(in io.Reader) error {
method MergeConfig (line 1661) | func (v *Viper) MergeConfig(in io.Reader) error {
method MergeConfigMap (line 1677) | func (v *Viper) MergeConfigMap(cfg map[string]any) error {
method WriteConfig (line 1690) | func (v *Viper) WriteConfig() error {
method SafeWriteConfig (line 1702) | func (v *Viper) SafeWriteConfig() error {
method WriteConfigAs (line 1713) | func (v *Viper) WriteConfigAs(filename string) error {
method WriteConfigTo (line 1721) | func (v *Viper) WriteConfigTo(w io.Writer) error {
method SafeWriteConfigAs (line 1735) | func (v *Viper) SafeWriteConfigAs(filename string) error {
method writeConfig (line 1743) | func (v *Viper) writeConfig(filename string, force bool) error {
method unmarshalReader (line 1781) | func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
method marshalWriter (line 1815) | func (v *Viper) marshalWriter(w io.Writer, configType string) error {
method AllKeys (line 1970) | func (v *Viper) AllKeys() []string {
method flattenAndMergeMap (line 1996) | func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[strin...
method mergeFlatMap (line 2029) | func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]any)...
method AllSettings (line 2053) | func (v *Viper) AllSettings() map[string]any {
method getSettings (line 2057) | func (v *Viper) getSettings(keys []string) map[string]any {
method SetFs (line 2080) | func (v *Viper) SetFs(fs afero.Fs) {
method SetConfigName (line 2090) | func (v *Viper) SetConfigName(in string) {
method SetConfigType (line 2107) | func (v *Viper) SetConfigType(in string) {
method SetConfigPermissions (line 2117) | func (v *Viper) SetConfigPermissions(perm os.FileMode) {
method getConfigType (line 2121) | func (v *Viper) getConfigType() string {
method getConfigFile (line 2140) | func (v *Viper) getConfigFile() (string, error) {
method Debug (line 2159) | func (v *Viper) Debug() { v.DebugTo(os.Stdout) }
method DebugTo (line 2162) | func (v *Viper) DebugTo(w io.Writer) {
function New (line 158) | func New() *Viper {
type Option (line 190) | type Option interface
type optionFunc (line 194) | type optionFunc
method apply (line 196) | func (fn optionFunc) apply(v *Viper) {
function KeyDelimiter (line 202) | func KeyDelimiter(d string) Option {
type StringReplacer (line 209) | type StringReplacer interface
function EnvKeyReplacer (line 215) | func EnvKeyReplacer(r StringReplacer) Option {
function WithDecodeHook (line 226) | func WithDecodeHook(h mapstructure.DecodeHookFunc) Option {
function NewWithOptions (line 237) | func NewWithOptions(opts ...Option) *Viper {
function SetOptions (line 251) | func SetOptions(opts ...Option) {
function Reset (line 260) | func Reset() {
function OnConfigChange (line 271) | func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
function WatchConfig (line 279) | func WatchConfig() { v.WatchConfig() }
function SetConfigFile (line 357) | func SetConfigFile(in string) { v.SetConfigFile(in) }
function SetEnvPrefix (line 370) | func SetEnvPrefix(in string) { v.SetEnvPrefix(in) }
function GetEnvPrefix (line 382) | func GetEnvPrefix() string { return v.GetEnvPrefix() }
function AllowEmptyEnv (line 400) | func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) }
function ConfigFileUsed (line 427) | func ConfigFileUsed() string { return v.ConfigFileUsed() }
function AddConfigPath (line 434) | func AddConfigPath(in string) { v.AddConfigPath(in) }
function SetTypeByDefaultValue (line 674) | func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
function GetViper (line 695) | func GetViper() *Viper {
function Get (line 706) | func Get(key string) any { return v.Get(key) }
function Sub (line 766) | func Sub(key string) *Viper { return v.Sub(key) }
function GetString (line 791) | func GetString(key string) string { return v.GetString(key) }
function GetBool (line 799) | func GetBool(key string) bool { return v.GetBool(key) }
function GetInt (line 807) | func GetInt(key string) int { return v.GetInt(key) }
function GetInt32 (line 815) | func GetInt32(key string) int32 { return v.GetInt32(key) }
function GetInt64 (line 823) | func GetInt64(key string) int64 { return v.GetInt64(key) }
function GetUint8 (line 831) | func GetUint8(key string) uint8 { return v.GetUint8(key) }
function GetUint (line 839) | func GetUint(key string) uint { return v.GetUint(key) }
function GetUint16 (line 847) | func GetUint16(key string) uint16 { return v.GetUint16(key) }
function GetUint32 (line 855) | func GetUint32(key string) uint32 { return v.GetUint32(key) }
function GetUint64 (line 863) | func GetUint64(key string) uint64 { return v.GetUint64(key) }
function GetFloat64 (line 871) | func GetFloat64(key string) float64 { return v.GetFloat64(key) }
function GetTime (line 879) | func GetTime(key string) time.Time { return v.GetTime(key) }
function GetDuration (line 887) | func GetDuration(key string) time.Duration { return v.GetDuration(key) }
function GetIntSlice (line 895) | func GetIntSlice(key string) []int { return v.GetIntSlice(key) }
function GetStringSlice (line 903) | func GetStringSlice(key string) []string { return v.GetStringSlice(key) }
function GetStringMap (line 911) | func GetStringMap(key string) map[string]any { return v.GetStringMap(key) }
function GetStringMapString (line 919) | func GetStringMapString(key string) map[string]string { return v.GetStri...
function GetStringMapStringSlice (line 927) | func GetStringMapStringSlice(key string) map[string][]string { return v....
function GetSizeInBytes (line 936) | func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) }
function UnmarshalKey (line 946) | func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) e...
function Unmarshal (line 957) | func Unmarshal(rawVal any, opts ...DecoderConfigOption) error {
function stringToWeakSliceHookFunc (line 1029) | func stringToWeakSliceHookFunc(sep string) mapstructure.DecodeHookFunc {
function decode (line 1049) | func decode(input any, config *mapstructure.DecoderConfig) error {
function UnmarshalExact (line 1059) | func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error {
function BindPFlags (line 1087) | func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) }
function BindPFlag (line 1100) | func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(...
function BindFlagValues (line 1116) | func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(...
function BindFlagValue (line 1130) | func BindFlagValue(key string, flag FlagValue) error { return v.BindFlag...
function BindEnv (line 1147) | func BindEnv(input ...string) error { return v.BindEnv(input...) }
function MustBindEnv (line 1174) | func MustBindEnv(input ...string) { v.MustBindEnv(input...) }
function readAsCSV (line 1375) | func readAsCSV(val string) ([]string, error) {
function stringToStringConv (line 1386) | func stringToStringConv(val string) any {
function stringToIntConv (line 1410) | func stringToIntConv(val string) any {
function IsSet (line 1434) | func IsSet(key string) bool { return v.IsSet(key) }
function AutomaticEnv (line 1446) | func AutomaticEnv() { v.AutomaticEnv() }
function SetEnvKeyReplacer (line 1457) | func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) }
function RegisterAlias (line 1468) | func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) }
function InConfig (line 1519) | func InConfig(key string) bool { return v.InConfig(key) }
function SetDefault (line 1535) | func SetDefault(key string, value any) { v.SetDefault(key, value) }
function Set (line 1557) | func Set(key string, value any) { v.Set(key, value) }
function ReadInConfig (line 1578) | func ReadInConfig() error { return v.ReadInConfig() }
function MergeInConfig (line 1616) | func MergeInConfig() error { return v.MergeInConfig() }
function ReadConfig (line 1640) | func ReadConfig(in io.Reader) error { return v.ReadConfig(in) }
function MergeConfig (line 1658) | func MergeConfig(in io.Reader) error { return v.MergeConfig(in) }
function MergeConfigMap (line 1673) | func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(...
function WriteConfig (line 1687) | func WriteConfig() error { return v.WriteConfig() }
function SafeWriteConfig (line 1699) | func SafeWriteConfig() error { return v.SafeWriteConfig() }
function WriteConfigAs (line 1710) | func WriteConfigAs(filename string) error { return v.WriteConfigAs(filen...
function WriteConfigTo (line 1718) | func WriteConfigTo(w io.Writer) error { return v.WriteConfigTo(w) }
function SafeWriteConfigAs (line 1732) | func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfig...
function keyExists (line 1836) | func keyExists(k string, m map[string]any) string {
function castToMapStringInterface (line 1847) | func castToMapStringInterface(
function castMapStringSliceToMapInterface (line 1857) | func castMapStringSliceToMapInterface(src map[string][]string) map[strin...
function castMapStringToMapInterface (line 1865) | func castMapStringToMapInterface(src map[string]string) map[string]any {
function castMapFlagToMapInterface (line 1873) | func castMapFlagToMapInterface(src map[string]FlagValue) map[string]any {
function mergeMaps (line 1886) | func mergeMaps(src, tgt map[string]any, itgt map[any]any) {
function AllKeys (line 1966) | func AllKeys() []string { return v.AllKeys() }
function AllSettings (line 2050) | func AllSettings() map[string]any { return v.AllSettings() }
function SetFs (line 2077) | func SetFs(fs afero.Fs) { v.SetFs(fs) }
function SetConfigName (line 2086) | func SetConfigName(in string) { v.SetConfigName(in) }
function SetConfigType (line 2103) | func SetConfigType(in string) { v.SetConfigType(in) }
function SetConfigPermissions (line 2114) | func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(per...
function Debug (line 2153) | func Debug() { v.Debug() }
function DebugTo (line 2156) | func DebugTo(w io.Writer) { v.DebugTo(w) }
FILE: viper_test.go
type testUnmarshalExtra (line 55) | type testUnmarshalExtra struct
function initConfigs (line 93) | func initConfigs(v *Viper) {
function initConfig (line 116) | func initConfig(typ, config string, v *Viper) {
function initDirs (line 126) | func initDirs(t *testing.T) (string, string) {
type stringValue (line 154) | type stringValue
method Set (line 161) | func (s *stringValue) Set(val string) error {
method Type (line 166) | func (s *stringValue) Type() string {
method String (line 170) | func (s *stringValue) String() string {
function newStringValue (line 156) | func newStringValue(val string, p *string) *stringValue {
function TestGetConfigFile (line 174) | func TestGetConfigFile(t *testing.T) {
function TestReadInConfig (line 358) | func TestReadInConfig(t *testing.T) {
function TestDefault (line 469) | func TestDefault(t *testing.T) {
function TestUnmarshaling (line 484) | func TestUnmarshaling(t *testing.T) {
function TestUnmarshalExact (line 500) | func TestUnmarshalExact(t *testing.T) {
function TestOverrides (line 510) | func TestOverrides(t *testing.T) {
function TestDefaultPost (line 516) | func TestDefaultPost(t *testing.T) {
function TestAliases (line 523) | func TestAliases(t *testing.T) {
function TestAliasInConfigFile (line 532) | func TestAliasInConfigFile(t *testing.T) {
function TestYML (line 547) | func TestYML(t *testing.T) {
function TestJSON (line 557) | func TestJSON(t *testing.T) {
function TestTOML (line 567) | func TestTOML(t *testing.T) {
function TestDotEnv (line 577) | func TestDotEnv(t *testing.T) {
function TestRemotePrecedence (line 586) | func TestRemotePrecedence(t *testing.T) {
function TestEnv (line 606) | func TestEnv(t *testing.T) {
function TestMultipleEnv (line 629) | func TestMultipleEnv(t *testing.T) {
function TestEmptyEnv (line 642) | func TestEmptyEnv(t *testing.T) {
function TestEmptyEnv_Allowed (line 657) | func TestEmptyEnv_Allowed(t *testing.T) {
function TestEnvPrefix (line 674) | func TestEnvPrefix(t *testing.T) {
function TestAutoEnv (line 697) | func TestAutoEnv(t *testing.T) {
function TestAutoEnvWithPrefix (line 707) | func TestAutoEnvWithPrefix(t *testing.T) {
function TestSetEnvKeyReplacer (line 715) | func TestSetEnvKeyReplacer(t *testing.T) {
function TestEnvKeyReplacer (line 727) | func TestEnvKeyReplacer(t *testing.T) {
function TestEnvSubConfig (line 734) | func TestEnvSubConfig(t *testing.T) {
function TestAllKeys (line 753) | func TestAllKeys(t *testing.T) {
function TestAllKeysWithEnv (line 824) | func TestAllKeysWithEnv(t *testing.T) {
function TestAliasesOfAliases (line 838) | func TestAliasesOfAliases(t *testing.T) {
function TestRecursiveAliases (line 846) | func TestRecursiveAliases(t *testing.T) {
function TestUnmarshal (line 854) | func TestUnmarshal(t *testing.T) {
function TestUnmarshalWithDefaultDecodeHook (line 897) | func TestUnmarshalWithDefaultDecodeHook(t *testing.T) {
function TestUnmarshalWithDecoderOptions (line 932) | func TestUnmarshalWithDecoderOptions(t *testing.T) {
function TestUnmarshalWithAutomaticEnv (line 967) | func TestUnmarshalWithAutomaticEnv(t *testing.T) {
function TestBindPFlags (line 1092) | func TestBindPFlags(t *testing.T) {
function TestBindPFlagsStringSlice (line 1125) | func TestBindPFlagsStringSlice(t *testing.T) {
function TestBindPFlagsStringArray (line 1169) | func TestBindPFlagsStringArray(t *testing.T) {
function TestBindPFlagsSlices (line 1213) | func TestBindPFlagsSlices(t *testing.T) {
function TestSliceFlagsReturnCorrectType (line 1236) | func TestSliceFlagsReturnCorrectType(t *testing.T) {
function TestBindPFlagsIntSlice (line 1252) | func TestBindPFlagsIntSlice(t *testing.T) {
function TestBindPFlag (line 1295) | func TestBindPFlag(t *testing.T) {
function TestBindPFlagDetectNilFlag (line 1316) | func TestBindPFlagDetectNilFlag(t *testing.T) {
function TestBindPFlagStringToString (line 1322) | func TestBindPFlagStringToString(t *testing.T) {
function TestBindPFlagStringToInt (line 1366) | func TestBindPFlagStringToInt(t *testing.T) {
function TestBoundCaseSensitivity (line 1410) | func TestBoundCaseSensitivity(t *testing.T) {
function TestSizeInBytes (line 1434) | func TestSizeInBytes(t *testing.T) {
function TestFindsNestedKeys (line 1451) | func TestFindsNestedKeys(t *testing.T) {
function TestReadConfig (line 1543) | func TestReadConfig(t *testing.T) {
function TestReadConfigWithSetConfigFile (line 1568) | func TestReadConfigWithSetConfigFile(t *testing.T) {
function TestWrongFileNotFound (line 1576) | func TestWrongFileNotFound(t *testing.T) {
function TestIsSet (line 1598) | func TestIsSet(t *testing.T) {
function TestDirsSearch (line 1645) | func TestDirsSearch(t *testing.T) {
function TestWrongDirsSearchNotFound (line 1666) | func TestWrongDirsSearchNotFound(t *testing.T) {
function TestWrongDirsSearchNotFoundForMerge (line 1690) | func TestWrongDirsSearchNotFoundForMerge(t *testing.T) {
function TestUnwrapParseErrors (line 1719) | func TestUnwrapParseErrors(t *testing.T) {
function TestSub (line 1725) | func TestSub(t *testing.T) {
function TestSubWithKeyDelimiter (line 1749) | func TestSubWithKeyDelimiter(t *testing.T) {
function TestWriteConfig (line 1799) | func TestWriteConfig(t *testing.T) {
function TestWriteConfigTOML (line 1877) | func TestWriteConfigTOML(t *testing.T) {
function TestWriteConfigDotEnv (line 1928) | func TestWriteConfigDotEnv(t *testing.T) {
function TestSafeWriteConfig (line 1977) | func TestSafeWriteConfig(t *testing.T) {
function TestSafeWriteConfigWithMissingConfigPath (line 1991) | func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
function TestSafeWriteConfigWithExistingFile (line 2000) | func TestSafeWriteConfigWithExistingFile(t *testing.T) {
function TestSafeWriteAsConfig (line 2014) | func TestSafeWriteAsConfig(t *testing.T) {
function TestSafeWriteConfigAsWithExistingFile (line 2026) | func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
function TestWriteHiddenFile (line 2037) | func TestWriteHiddenFile(t *testing.T) {
function TestMergeConfig (line 2098) | func TestMergeConfig(t *testing.T) {
function TestMergeConfigWithSetConfigFile (line 2127) | func TestMergeConfigWithSetConfigFile(t *testing.T) {
function TestMergeConfigOverrideType (line 2135) | func TestMergeConfigOverrideType(t *testing.T) {
function TestMergeConfigNoMerge (line 2148) | func TestMergeConfigNoMerge(t *testing.T) {
function TestMergeConfigMap (line 2168) | func TestMergeConfigMap(t *testing.T) {
function TestUnmarshalingWithAliases (line 2200) | func TestUnmarshalingWithAliases(t *testing.T) {
function TestSetConfigNameClearsFileCache (line 2223) | func TestSetConfigNameClearsFileCache(t *testing.T) {
function TestShadowedNestedValue (line 2232) | func TestShadowedNestedValue(t *testing.T) {
function TestDotParameter (line 2258) | func TestDotParameter(t *testing.T) {
function TestCaseInsensitive (line 2275) | func TestCaseInsensitive(t *testing.T) {
function TestCaseInsensitiveSet (line 2320) | func TestCaseInsensitiveSet(t *testing.T) {
function TestParseNested (line 2359) | func TestParseNested(t *testing.T) {
function doTestCaseInsensitive (line 2387) | func doTestCaseInsensitive(t *testing.T, typ, config string) {
function newViperWithConfigFile (line 2401) | func newViperWithConfigFile(t *testing.T) (*Viper, string) {
function newViperWithSymlinkedConfigFile (line 2414) | func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, stri...
function TestWatchFile (line 2438) | func TestWatchFile(t *testing.T) {
function TestUnmarshal_DotSeparatorBackwardCompatibility (line 2498) | func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
function TestKeyDelimiter (line 2535) | func TestKeyDelimiter(t *testing.T) {
function TestSliceIndexAccess (line 2609) | func TestSliceIndexAccess(t *testing.T) {
function TestIsPathShadowedInFlatMap (line 2633) | func TestIsPathShadowedInFlatMap(t *testing.T) {
function TestFlagShadow (line 2659) | func TestFlagShadow(t *testing.T) {
function BenchmarkGetBool (line 2678) | func BenchmarkGetBool(b *testing.B) {
function BenchmarkGet (line 2690) | func BenchmarkGet(b *testing.B) {
function BenchmarkGetBoolFromMap (line 2703) | func BenchmarkGetBoolFromMap(b *testing.B) {
function skipWindows (line 2716) | func skipWindows(t *testing.T) {
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (303K chars).
[
{
"path": ".editorconfig",
"chars": 271,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
},
{
"path": ".envrc",
"chars": 231,
"preview": "if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then\n source_url \"https://raw.githubusercontent.com/nix-comm"
},
{
"path": ".github/.editorconfig",
"chars": 33,
"preview": "[{*.yml,*.yaml}]\nindent_size = 2\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
"chars": 3471,
"preview": "name: 🐛 Bug report\ndescription: Report a bug to help us improve Viper\nlabels: [kind/bug]\nbody:\n- type: markdown\n attrib"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 454,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: ❓ Ask a question\n url: https://github.com/spf13/viper/discussion"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
"chars": 1490,
"preview": "name: 🎉 Feature request\ndescription: Suggest an idea for Viper\nlabels: [kind/enhancement]\nbody:\n- type: markdown\n attri"
},
{
"path": ".github/PULL_REQUEST_TEMPLATES.md",
"chars": 711,
"preview": "<!--\nThank you for sending a pull request! Here some tips for contributors:\n\n1. Fill the description template below.\n2. "
},
{
"path": ".github/dependabot.yaml",
"chars": 271,
"preview": "version: 2\n\nupdates:\n - package-ecosystem: gomod\n directory: /\n labels:\n - area/dependencies\n schedule:\n "
},
{
"path": ".github/octoslash/policies/collaborator.cedar",
"chars": 66,
"preview": "permit(\n\tprincipal in Role::\"Collaborator\",\n\taction,\n\tresource\n);\n"
},
{
"path": ".github/octoslash/policies/triager.cedar",
"chars": 175,
"preview": "permit(\n\tprincipal in Role::\"Triager\",\n\taction in [Action::\"close\", Action::\"add-label\", Action::\"remove-label\", Action:"
},
{
"path": ".github/octoslash/principals.json",
"chars": 341,
"preview": "[\n {\n \"uid\": { \"type\": \"User\", \"id\": \"1226384\" },\n \"attrs\": { \"login\": \"sagikazarmark\" },\n \"pare"
},
{
"path": ".github/release.yml",
"chars": 713,
"preview": "changelog:\n exclude:\n labels:\n - release-note/ignore\n categories:\n - title: Exciting New Features 🎉\n l"
},
{
"path": ".github/workflows/checks.yaml",
"chars": 823,
"preview": "name: PR Checks\n\non:\n pull_request:\n types: [opened, labeled, unlabeled, synchronize]\n\npermissions:\n pull-requests:"
},
{
"path": ".github/workflows/ci.yaml",
"chars": 3076,
"preview": "name: CI\n\non:\n push:\n branches: [master]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n build:\n name: Bu"
},
{
"path": ".github/workflows/octoslash.yaml",
"chars": 342,
"preview": "name: Octoslash\n\non: issue_comment\n\npermissions:\n issues: write\n pull-requests: write\n\njobs:\n run:\n name: Run\n "
},
{
"path": ".github/workflows/stale.yaml",
"chars": 897,
"preview": "name: \"Close stale issues\"\n\non:\n schedule:\n - cron: \"0 3 * * *\"\n\npermissions:\n actions: write\n issues: write\n # c"
},
{
"path": ".gitignore",
"chars": 82,
"preview": "/.devenv/\n/.direnv/\n/.idea/\n/.pre-commit-config.yaml\n/bin/\n/build/\n/var/\n/vendor/\n"
},
{
"path": ".golangci.yaml",
"chars": 2990,
"preview": "version: \"2\"\n\nrun:\n timeout: 5m\n\nlinters:\n enable:\n - bodyclose\n - dogsled\n - dupl\n - durationcheck\n - "
},
{
"path": ".yamlignore",
"chars": 24,
"preview": "# TODO: FIXME\n/.github/\n"
},
{
"path": ".yamllint.yaml",
"chars": 93,
"preview": "ignore-from-file: [.gitignore, .yamlignore]\n\nextends: default\n\nrules:\n line-length: disable\n"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Steve Francia\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "Makefile",
"chars": 2430,
"preview": "# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html\n\nOS = $(shell uname | t"
},
{
"path": "README.md",
"chars": 21376,
"preview": "> ## Viper v2 Feedback\n>\n> Viper is heading towards v2 and we would love to hear what _**you**_ would\n> like to see in i"
},
{
"path": "TROUBLESHOOTING.md",
"chars": 1756,
"preview": "# Troubleshooting\n\n## Unmarshaling doesn't work\n\nThe most common reason for this issue is improper use of struct tags (e"
},
{
"path": "UPGRADE.md",
"chars": 4622,
"preview": "# Update Log\n\n**This document details any major updates required to use new features or improvements in Viper.**\n\n## v1."
},
{
"path": "encoding.go",
"chars": 4013,
"preview": "package viper\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/spf13/viper/internal/encoding/dotenv\"\n\t\"github.com/sp"
},
{
"path": "encoding_test.go",
"chars": 1937,
"preview": "package viper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype"
},
{
"path": "errors.go",
"chars": 2591,
"preview": "package viper\n\nimport (\n\t\"fmt\"\n)\n\n// FileLookupError is returned when Viper cannot resolve a configuration file.\n//\n// T"
},
{
"path": "experimental.go",
"chars": 204,
"preview": "package viper\n\n// ExperimentalBindStruct tells Viper to use the new bind struct feature.\nfunc ExperimentalBindStruct() O"
},
{
"path": "file.go",
"chars": 2619,
"preview": "package viper\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/sagikazarmark/locafero\"\n\t\"github.com/spf13/afero\"\n)\n\n// Exp"
},
{
"path": "finder.go",
"chars": 1002,
"preview": "package viper\n\nimport (\n\t\"errors\"\n\n\t\"github.com/spf13/afero\"\n)\n\n// WithFinder sets a custom [Finder].\nfunc WithFinder(f "
},
{
"path": "finder_example_test.go",
"chars": 1614,
"preview": "package viper_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/sagikazarmark/locafero\"\n\t\"github.com/spf13/afero\"\n\n\t\"github.com/spf13/"
},
{
"path": "finder_test.go",
"chars": 679,
"preview": "package viper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/afero\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretch"
},
{
"path": "flags.go",
"chars": 1345,
"preview": "package viper\n\nimport \"github.com/spf13/pflag\"\n\n// FlagValueSet is an interface that users can implement\n// to bind a se"
},
{
"path": "flags_test.go",
"chars": 1362,
"preview": "package viper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/pflag\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretch"
},
{
"path": "flake.nix",
"chars": 1337,
"preview": "{\n description = \"Viper\";\n\n inputs = {\n nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n flake-parts.url "
},
{
"path": "go.mod",
"chars": 747,
"preview": "module github.com/spf13/viper\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/go-viper/mapstructu"
},
{
"path": "go.sum",
"chars": 3691,
"preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
},
{
"path": "internal/encoding/dotenv/codec.go",
"chars": 1194,
"preview": "package dotenv\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/subosito/gotenv\"\n)\n\nconst keyDelimiter = \"_\"\n\n"
},
{
"path": "internal/encoding/dotenv/codec_test.go",
"chars": 934,
"preview": "package dotenv\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// "
},
{
"path": "internal/encoding/dotenv/map_utils.go",
"chars": 966,
"preview": "package dotenv\n\nimport (\n\t\"strings\"\n\n\t\"github.com/spf13/cast\"\n)\n\n// flattenAndMergeMap recursively flattens the given ma"
},
{
"path": "internal/encoding/json/codec.go",
"chars": 521,
"preview": "package json\n\nimport (\n\t\"encoding/json\"\n)\n\n// Codec implements the encoding.Encoder and encoding.Decoder interfaces for "
},
{
"path": "internal/encoding/json/codec_test.go",
"chars": 1338,
"preview": "package json\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// en"
},
{
"path": "internal/encoding/toml/codec.go",
"chars": 463,
"preview": "package toml\n\nimport (\n\t\"github.com/pelletier/go-toml/v2\"\n)\n\n// Codec implements the encoding.Encoder and encoding.Decod"
},
{
"path": "internal/encoding/toml/codec_test.go",
"chars": 1482,
"preview": "package toml\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// or"
},
{
"path": "internal/encoding/yaml/codec.go",
"chars": 445,
"preview": "package yaml\n\nimport \"go.yaml.in/yaml/v3\"\n\n// Codec implements the encoding.Encoder and encoding.Decoder interfaces for "
},
{
"path": "internal/encoding/yaml/codec_test.go",
"chars": 2015,
"preview": "package yaml\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// or"
},
{
"path": "internal/features/bind_struct.go",
"chars": 158,
"preview": "//go:build viper_bind_struct\n\npackage features\n\n// BindStruct is a feature flag for enabling/disabling the config bindin"
},
{
"path": "internal/features/bind_struct_default.go",
"chars": 160,
"preview": "//go:build !viper_bind_struct\n\npackage features\n\n// BindStruct is a feature flag for enabling/disabling the config bindi"
},
{
"path": "internal/features/finder.go",
"chars": 133,
"preview": "//go:build viper_finder\n\npackage features\n\n// Finder is a feature flag for enabling/disabling the config finder.\nconst F"
},
{
"path": "internal/features/finder_default.go",
"chars": 135,
"preview": "//go:build !viper_finder\n\npackage features\n\n// Finder is a feature flag for enabling/disabling the config finder.\nconst "
},
{
"path": "internal/testutil/filepath.go",
"chars": 245,
"preview": "package testutil\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n)\n\n// AbsFilePath calls filepath.Abs on path.\nfunc AbsFilePath(t "
},
{
"path": "logger.go",
"chars": 544,
"preview": "package viper\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n)\n\n// WithLogger sets a custom logger.\nfunc WithLogger(l *slog.Logger) Op"
},
{
"path": "overrides_test.go",
"chars": 6162,
"preview": "package viper\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/spf13/cast\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype la"
},
{
"path": "remote/go.mod",
"chars": 4119,
"preview": "module github.com/spf13/viper/remote\n\ngo 1.23.8\n\nreplace github.com/spf13/viper => ../\n\nrequire (\n\tgithub.com/sagikazarm"
},
{
"path": "remote/go.sum",
"chars": 35743,
"preview": "cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=\ncloud.google.com/go v0.120.0/go.mod h1:/beW"
},
{
"path": "remote/remote.go",
"chars": 2965,
"preview": "// Copyright © 2015 Steve Francia <spf@spf13.com>.\n//\n// Use of this source code is governed by an MIT-style\n// license "
},
{
"path": "remote.go",
"chars": 9328,
"preview": "package viper\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"slices\"\n)\n\n// SupportedRemoteProviders are universally suppor"
},
{
"path": "util.go",
"chars": 5003,
"preview": "// Copyright © 2014 Steve Francia <spf@spf13.com>.\n//\n// Use of this source code is governed by an MIT-style\n// license "
},
{
"path": "util_test.go",
"chars": 1943,
"preview": "// Copyright © 2016 Steve Francia <spf@spf13.com>.\n//\n// Use of this source code is governed by an MIT-style\n// license "
},
{
"path": "viper.go",
"chars": 64403,
"preview": "// Copyright © 2014 Steve Francia <spf@spf13.com>.\n//\n// Use of this source code is governed by an MIT-style\n// license "
},
{
"path": "viper_test.go",
"chars": 66541,
"preview": "// Copyright © 2014 Steve Francia <spf@spf13.com>.\n//\n// Use of this source code is governed by an MIT-style\n// license "
},
{
"path": "viper_yaml_test.go",
"chars": 781,
"preview": "package viper\n\nvar yamlExample = []byte(`Hacker: true\nname: steve\nhobbies:\n - skateboarding\n - snowboarding\n - "
}
]
About this extraction
This page contains the full source code of the spf13/viper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (272.1 KB), approximately 89.5k tokens, and a symbol index with 436 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.