Compare commits

...
156 Commits
Author SHA1 Message Date
Mark Sagi-KazarandMárk Sági-Kazár b89e554a96 chore: update crypt
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-06 14:19:33 +01:00
Mark Sagi-KazarandMárk Sági-Kazár db9f89ac41 chore: disable watch on appengine
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-06 03:22:10 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 4b8d14881e refactor: use new Has fsnotify method for event matching
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 05:39:26 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 2e99a57324 refactor: rename watch file to unsupported
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 03:17:49 +01:00
Mark Sagi-KazarandMárk Sági-Kazár dcb7f30f39 feat: fix compilation for all platforms unsupported by fsnotify
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 03:17:49 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 2e04739b68 ci: drop dedicated wasm build
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 03:17:49 +01:00
Mark Sagi-KazarandMárk Sági-Kazár b2234f214f ci: add build for aix
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 03:17:49 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 52009d3493 feat: disable watcher on aix
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-11-04 03:17:49 +01:00
dependabot[bot]andMárk Sági-Kazár b274f639e0 build(deps): bump github.com/fsnotify/fsnotify from 1.5.4 to 1.6.0
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.5.4 to 1.6.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.5.4...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-03 17:20:38 +01:00
dependabot[bot]andMárk Sági-Kazár 7c62cfdbac build(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-03 17:19:37 +01:00
dependabot[bot] f1d2c470bf build(deps): bump github.com/sagikazarmark/crypt from 0.6.0 to 0.7.0
Bumps [github.com/sagikazarmark/crypt](https://github.com/sagikazarmark/crypt) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/sagikazarmark/crypt/releases)
- [Commits](https://github.com/sagikazarmark/crypt/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: github.com/sagikazarmark/crypt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-27 21:07:24 +00:00
dependabot[bot]andMárk Sági-Kazár 419fd86e49 build(deps): bump github.com/spf13/afero from 1.8.2 to 1.9.2
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.8.2 to 1.9.2.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.8.2...v1.9.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-06 14:46:54 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 91aa484d1d chore: drop support for Go 1.15
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-06 14:28:48 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 57cc9a000f test: fix ini tests
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-06 11:57:54 +02:00
dependabot[bot]andMárk Sági-Kazár 8030d5b976 build(deps): bump gopkg.in/ini.v1 from 1.66.4 to 1.67.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.66.4 to 1.67.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.66.4...v1.67.0)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-06 11:57:54 +02:00
Brad P. CrochetandMárk Sági-Kazár 312417a0c5 Add a DebugTo convenience funtion
One might want to write the debug information somewhere other than
Stdout. This patch adss a DebugTo function and method, that accepts
an io.Writer. It changes the original Debug implementation to call
this new function with a default of os.Stdout, which maintains
backward compatibility.

Signed-off-by: Brad P. Crochet <brad@redhat.com>
2022-09-06 11:14:22 +02:00
Christian BanseandMárk Sági-Kazár 202060b3a2 Adds support for uint16 with GetUint16
We have encountered numerous places where it is convenient to have viper return a `uint16` value, especially in combination with the new `netip` package  that represents a port correctly as `uint16` rather than just an `int`. cobra already supports this, but we need a conversion from the existing `GetUint` method in viper.
2022-09-04 03:07:23 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 97591f0083 build: fix lint violations
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-03 19:07:15 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 9af8daeeab ci: upgrade golangci-lint
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-03 19:07:15 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 7b4f2b27cd ci: add Go 1.19 to CI
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-03 19:07:15 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 601ec815ba test: fix toml tests
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-09-03 17:33:50 +02:00
dependabot[bot]andMárk Sági-Kazár d7f4832bd3 build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.2 to 2.0.5
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.2 to 2.0.5.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.2...v2.0.5)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-03 17:33:50 +02:00
dependabot[bot]andMárk Sági-Kazár c2f42f3060 build(deps): bump github.com/subosito/gotenv from 1.4.0 to 1.4.1
Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/subosito/gotenv/releases)
- [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/subosito/gotenv/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/subosito/gotenv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-03 13:54:00 +02:00
Andrew RichardsonandMárk Sági-Kazár 5247643f02 Recurse into arrays when converting keys to lowercase
Fixes #1386

Signed-off-by: Andrew Richardson <andrew.richardson@kaleido.io>
2022-07-12 18:10:05 +02:00
Kevin Franklin KimandMárk Sági-Kazár 98c63ede11 feat: check providers and log error 2022-07-10 13:48:18 +02:00
Kevin Franklin KimandMárk Sági-Kazár 1bc0a5ac7a add etcd3 to supported providers 2022-07-10 13:48:18 +02:00
Sungyun HurandMárk Sági-Kazár a4e4f65a0c Update README.md 2022-07-10 12:51:24 +02:00
dependabot[bot]andMárk Sági-Kazár d55cd57115 build(deps): bump github.com/stretchr/testify from 1.7.4 to 1.8.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.4 to 1.8.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.4...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-10 12:35:00 +02:00
dependabot[bot]andMárk Sági-Kazár 0add84cdf2 build(deps): bump mheap/github-action-required-labels from 1 to 2
Bumps [mheap/github-action-required-labels](https://github.com/mheap/github-action-required-labels) from 1 to 2.
- [Release notes](https://github.com/mheap/github-action-required-labels/releases)
- [Commits](https://github.com/mheap/github-action-required-labels/compare/v1...v2)

---
updated-dependencies:
- dependency-name: mheap/github-action-required-labels
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-21 20:15:53 +02:00
dependabot[bot]andMárk Sági-Kazár 501b966b5d build(deps): bump github.com/stretchr/testify from 1.7.3 to 1.7.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.3 to 1.7.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.3...v1.7.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-21 20:15:04 +02:00
dependabot[bot]andMárk Sági-Kazár bd03e926b9 build(deps): bump github.com/stretchr/testify from 1.7.2 to 1.7.3
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.2...v1.7.3)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-20 20:02:32 +02:00
mjmaiseyandMárk Sági-Kazár b1b8b3d4d4 Fix go-staticcheck failures (ST1005)
Fix to example code, which fails static checks related to acceptable formats of error strings if used.
2022-06-16 21:19:37 +02:00
dependabot[bot]andMárk Sági-Kazár 2b83d46130 build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.1 to 2.0.2
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-09 19:50:27 +02:00
dependabot[bot]andMárk Sági-Kazár 22b9573a99 build(deps): bump github.com/subosito/gotenv from 1.3.0 to 1.4.0
Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/subosito/gotenv/releases)
- [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/subosito/gotenv/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/subosito/gotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-07 20:44:09 +02:00
dependabot[bot]andMárk Sági-Kazár 32d4cbb62f build(deps): bump github.com/stretchr/testify from 1.7.1 to 1.7.2
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-07 13:01:18 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 4322cf20e9 feat: make toml2 the default
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-05-26 17:48:00 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 8d0299919d feat: make yaml3 the default
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-05-26 17:27:50 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 7c35aa91d2 chore(deps): update yaml3
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-05-26 17:27:50 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 433821fa47 feat: add etcd3 support to remote
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-05-26 17:01:59 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 2080d43fa5 chore: update crypt
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-05-26 17:01:59 +02:00
Wade CarpenterandMárk Sági-Kazár da55858fff chore: fix Error log calls in mergeMaps
The logger interface uses k,v pairs, but it was being called with a
format string, here.

Changed to use k,v pairs.

I didn't find any other instances of this problem.
2022-05-26 16:01:47 +02:00
Michael WilsonandMárk Sági-Kazár f50ce904a9 Add in MustBindEnv.
Adds in a MustBindEnv function which will panic if BindEnv fails.
2022-05-26 15:53:50 +02:00
dependabot[bot]andMárk Sági-Kazár 3b836e5088 build(deps): bump github.com/subosito/gotenv from 1.2.0 to 1.3.0
Bumps [github.com/subosito/gotenv](https://github.com/subosito/gotenv) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/subosito/gotenv/releases)
- [Changelog](https://github.com/subosito/gotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/subosito/gotenv/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/subosito/gotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-26 15:47:59 +02:00
dependabot[bot]andMárk Sági-Kazár 5d65186f1e build(deps): bump github.com/pelletier/go-toml/v2 from 2.0.0 to 2.0.1
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-12 15:36:00 +02:00
dependabot[bot]andMárk Sági-Kazár 9f85518c5d build(deps): bump github.com/spf13/cast from 1.4.1 to 1.5.0
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-12 11:46:19 +02:00
dependabot[bot]andMárk Sági-Kazár ffad330c91 build(deps): bump github.com/fsnotify/fsnotify from 1.5.1 to 1.5.4
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.5.1 to 1.5.4.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.5.1...v1.5.4)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:31:41 +02:00
dependabot[bot]andMárk Sági-Kazár 5f023c7e56 build(deps): bump github.com/pelletier/go-toml from 1.9.4 to 1.9.5
Bumps [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Commits](https://github.com/pelletier/go-toml/compare/v1.9.4...v1.9.5)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:29:38 +02:00
dependabot[bot]andMárk Sági-Kazár 55a46638f2 build(deps): bump github.com/mitchellh/mapstructure from 1.4.3 to 1.5.0
Bumps [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) from 1.4.3 to 1.5.0.
- [Release notes](https://github.com/mitchellh/mapstructure/releases)
- [Changelog](https://github.com/mitchellh/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mitchellh/mapstructure/compare/v1.4.3...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/mitchellh/mapstructure
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 16:55:51 +02:00
dependabot[bot]andMárk Sági-Kazár 4eb3e0bc2f build(deps): bump github.com/pelletier/go-toml/v2
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.0-beta.8 to 2.0.0.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.0-beta.8...v2.0.0)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 12:13:36 +02:00
dependabot[bot]andMárk Sági-Kazár 81089eeca9 build(deps): bump github/codeql-action from 1 to 2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-27 03:35:43 +02:00
Mark Sagi-Kazar 5243f253fc chore: rename files to yaml
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-14 01:05:39 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 6986c0ab48 chore: update crypt
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 14:14:11 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 65293ecec2 add release note configuration
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 13:37:59 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 6804da723f chore!: drop Go 1.14 support
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 13:37:21 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 5b21ca137d fix: deprecated config
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 13:22:15 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 55fac1047e chore: fix lint
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 13:22:15 +02:00
Mark Sagi-KazarandMárk Sági-Kazár e0bf4ac05d chore: add go 1.18 builds
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-04-13 13:22:15 +02:00
dependabot[bot]andMárk Sági-Kazár 973c265115 build(deps): bump github.com/pelletier/go-toml/v2
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.0-beta.7 to 2.0.0-beta.8.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.0-beta.7...v2.0.0-beta.8)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-11 20:43:57 +02:00
dependabot[bot]andMárk Sági-Kazár 129e4f973c build(deps): bump github.com/pelletier/go-toml/v2
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.0-beta.6 to 2.0.0-beta.7.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.0-beta.6...v2.0.0-beta.7)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-10 14:25:58 +02:00
dependabot[bot]andMárk Sági-Kazár 9a8603d8f8 build(deps): bump actions/setup-go from 2 to 3
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 21:49:16 +02:00
dependabot[bot]andMárk Sági-Kazár dc76f3c0a9 build(deps): bump github.com/spf13/afero from 1.8.1 to 1.8.2
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-12 11:16:20 +01:00
dependabot[bot]andMárk Sági-Kazár 343434eb50 build(deps): bump gopkg.in/ini.v1 from 1.66.3 to 1.66.4
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.66.3 to 1.66.4.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.66.3...v1.66.4)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-05 17:53:31 +01:00
dependabot[bot]andMárk Sági-Kazár 860326d9e7 build(deps): bump github.com/magiconair/properties from 1.8.5 to 1.8.6
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.5 to 1.8.6.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Changelog](https://github.com/magiconair/properties/blob/main/CHANGELOG.md)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.5...v1.8.6)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-04 10:42:28 +01:00
dependabot[bot]andMárk Sági-Kazár bba19c61d6 build(deps): bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-04 10:36:48 +01:00
dependabot[bot]andMárk Sági-Kazár 5986bd9c0c build(deps): bump actions/github-script from 5 to 6
Bumps [actions/github-script](https://github.com/actions/github-script) from 5 to 6.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 11:15:50 +01:00
dependabot[bot]andMárk Sági-Kazár 9f6bc5da85 build(deps): bump github.com/spf13/afero from 1.8.0 to 1.8.1
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 21:23:03 +01:00
dependabot[bot]andMárk Sági-Kazár 9fbab61cfd build(deps): bump gopkg.in/ini.v1 from 1.66.2 to 1.66.3
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.66.2 to 1.66.3.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.66.2...v1.66.3)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-25 23:37:22 +01:00
Mark Sagi-Kazar daf9560784 add the troubleshooting guide to the bug report template
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-01-19 11:14:26 +01:00
Mark Sagi-Kazar 5f0a660bb4 add yaml y-n issue to the troubleshooting guide
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-01-19 11:11:16 +01:00
Illarion KovalchukandMárk Sági-Kazár 97664ba020 Added test case for merging configs, where target is null, and source is not null 2022-01-11 22:36:46 +01:00
illarion KovalchukandMárk Sági-Kazár 0353c6ea50 Allow merging configs with different types of leaf values 2022-01-11 22:36:46 +01:00
dependabot[bot]andMárk Sági-Kazár 788403a47c build(deps): bump github.com/spf13/afero from 1.7.1 to 1.8.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-11 18:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 5924acc506 feat: experimental logger
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-01-01 19:02:51 +01:00
Mark Sagi-KazarandMárk Sági-Kazár b13f0963f6 feat(encoding): experimental toml v2 support
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-01-01 18:45:36 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 98c10c3c31 test(encoding): fix failing tests due to the yaml library update
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2022-01-01 18:16:38 +01:00
Mark Sagi-KazarandMárk Sági-Kazár f8a13cf704 feat(encoding): experimental yaml v3 library support 2022-01-01 18:16:38 +01:00
dependabot[bot]andMárk Sági-Kazár 9934fe79e7 build(deps): bump github.com/spf13/afero from 1.7.0 to 1.7.1
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-29 18:40:32 +01:00
dependabot[bot]andMárk Sági-Kazár a0bc8ee4c3 build(deps): bump github.com/spf13/afero from 1.6.0 to 1.7.0
Bumps [github.com/spf13/afero](https://github.com/spf13/afero) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/spf13/afero/releases)
- [Commits](https://github.com/spf13/afero/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/afero
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 11:35:40 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 7b68d33505 ci: disable race detector on windows 2021-12-28 09:43:14 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 04d3a0cb02 feat(encoding): integrate dotenv codec into Viper
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 1d11247e33 feat(encoding): add dotenv codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 72453f720e feat(encoding): integrate Java properties codec into Viper
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 858ffb6bd0 feat(encoding): add Java properties codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 430936044e feat(encoding): integrate ini codec into Viper
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár dd62da434f refactor(encoding): initialize codecs per Viper
Some codecs might have options that rely on Viper in the future
(eg. key delimiter) which requires initializing codecs
for each Viper instance.

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 38a4fbd769 feat(encoding): add ini codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár e1924e3858 test(encoding): add tests for existing encoding implementations
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 4b307cc0f3 feat(encoding)!: accept a map in the encoder interface
This interface is specific to encoding data from Viper's internal,
so it's okay to make it Viper specific.

BREAKING CHANGE: the encoder interface now accepts a map instead of an interface

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Mark Sagi-KazarandMárk Sági-Kazár e54e7a53a5 feat(encoding)!: accept a map in the decoder interface
This interface is specific to decoding data into Viper's internal,
so it's okay to make it Viper specific.

BREAKING CHANGE: the decoder interface now accepts a map instead of an
interface

Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 22:31:11 +01:00
Stephen WodeckiandMárk Sági-Kazár 6c1745665b unnecessary operand 2021-12-15 22:26:56 +01:00
Mark Sagi-KazarandMárk Sági-Kazár f646c50b18 chore(deps): update dependencies
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-12-15 14:10:06 +01:00
Mark Sagi-KazarandMárk Sági-Kazár a4bfcd9ea0 chore(deps): update crypt 2021-12-12 04:00:30 +01:00
dependabot[bot]andMárk Sági-Kazár 1cb6606f6e build(deps): bump gopkg.in/ini.v1 from 1.65.0 to 1.66.2
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.65.0 to 1.66.2.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.65.0...v1.66.2)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-12 02:56:38 +01:00
Mark Sagi-KazarandMárk Sági-Kazár a785a79f22 refactor: replace jww with the new logger interface 2021-12-09 18:58:22 +01:00
Mark Sagi-KazarandMárk Sági-Kazár f1f6b2122c feat: add logger interface and default implementation 2021-12-09 18:58:22 +01:00
dependabot[bot]andMárk Sági-Kazár c43197d858 build(deps): bump github.com/mitchellh/mapstructure from 1.4.2 to 1.4.3
Bumps [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/mitchellh/mapstructure/releases)
- [Changelog](https://github.com/mitchellh/mapstructure/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitchellh/mapstructure/compare/v1.4.2...v1.4.3)

---
updated-dependencies:
- dependency-name: github.com/mitchellh/mapstructure
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-02 22:22:27 +01:00
dependabot[bot]andMárk Sági-Kazár 2abe0ddbd4 build(deps): bump gopkg.in/ini.v1 from 1.64.0 to 1.65.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 22:19:52 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 8ec82f8998 chore(deps): update crypt
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 15:17:20 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 35877c8f77 chore: fix lint
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 15:05:44 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 655a0aa730 chore(deps): update golangci-lint
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 15:05:44 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 946ae75247 ci: fix github script
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 15:04:54 +01:00
Mark Sagi-KazarandMárk Sági-Kazár d40d641ca7 chore: Go 1.17 update
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 14:51:34 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 41ec2aaf27 chore: run lint fixer
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 14:51:34 +01:00
Mark Sagi-KazarandMárk Sági-Kazár ba606cd9f1 chore: update build tools
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-11-21 14:51:34 +01:00
dependabot[bot]andMárk Sági-Kazár 91b237fdbf build(deps): bump actions/github-script from 4 to 5
Bumps [actions/github-script](https://github.com/actions/github-script) from 4 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-21 14:42:33 +01:00
dependabot[bot]andMárk Sági-Kazár 793ee22246 build(deps): bump gopkg.in/ini.v1 from 1.63.2 to 1.64.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.63.2...v1.64.0)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-12 18:27:17 +01:00
Mark Sagi-KazarandMárk Sági-Kazár 0594522560 test: fix failing tests on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 20:41:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 14c9dc6a57 test: fix file find tests on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 20:41:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár eae2327cb5 feat: disable finder for now
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 20:41:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 237f373936 test: fix finder tests on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 20:41:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 78228f56c5 build: disable goconst linter for now
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 8b7777d3c6 test(windows): skip failing tests on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 15c88da25c test(windows): fix watch file test
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 552e3bd0ca ci: separate lint and test
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 1a7008fa32 ci: make build pass on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 077ee305c4 ci: run tests on windows
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-22 11:17:33 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 5a4e555471 test: add tests for reading config files
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 18:01:05 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 08ba8ca7fc ci: disable fail fast
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 18:01:05 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 4e595cec77 feat: use io/fs for searching files on Go 1.16+
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 18:01:05 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 557c5d64e0 test: improve tests for searching files
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 18:01:05 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 8e71595a4a feat: implement new finder using io/fs
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 18:01:05 +02:00
Mark Sagi-KazarandMárk Sági-Kazár ce82267a11 refactor: drop unused Unsetenv from testutil
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 11:27:27 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 558a299a01 feat: use TB.Setenv on Go 1.17
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-21 11:27:27 +02:00
Jim Razmus IIandMárk Sági-Kazár b1fdc47b0d Recognize tfvars files as hcl by default.
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-19 23:25:03 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 65f16c1738 docs: fix indentation in code samples
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-19 22:10:43 +02:00
dependabot[bot]andMárk Sági-Kazár 0d7e8034ee build(deps): bump github.com/mitchellh/mapstructure from 1.4.1 to 1.4.2
Bumps [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/mitchellh/mapstructure/releases)
- [Changelog](https://github.com/mitchellh/mapstructure/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitchellh/mapstructure/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: github.com/mitchellh/mapstructure
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-19 09:30:36 +02:00
dependabot[bot]andMárk Sági-Kazár 6a29539e59 build(deps): bump gopkg.in/ini.v1 from 1.63.1 to 1.63.2
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.63.1 to 1.63.2.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.63.1...v1.63.2)

---
updated-dependencies:
- dependency-name: gopkg.in/ini.v1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-19 09:30:24 +02:00
Séra ZoltánandMárk Sági-Kazár fa3412d7ea fix cannot write hidden file without extension (#1017) 2021-09-19 09:28:14 +02:00
Séra ZoltánandMárk Sági-Kazár a1f26b11bd Add test for cannot write hidden file without extension 2021-09-19 09:28:14 +02:00
Séra ZoltánandMárk Sági-Kazár 46a61e6fbd Fixes #1062 2021-09-19 09:08:21 +02:00
Vasily OvchinnikovandMárk Sági-Kazár e606f7496e fix: made InConfig process paths correctly 2021-09-16 10:32:19 +02:00
Márk Sági-KazárandGitHub 2062cd6ee6 Fix codeql workflow 2021-09-15 21:19:19 +02:00
Mark Sagi-KazarandMárk Sági-Kazár c4687f7766 Add CodeQL analysis workflow
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-15 21:08:35 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 5a4d2a0519 chore(deps): update crypt library fork
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-15 21:08:07 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 6937864bf6 chore(deps): update go-ini dependency
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-09-15 18:49:09 +02:00
dependabot[bot]andMárk Sági-Kazár e897cbf546 build(deps): bump github.com/pelletier/go-toml from 1.9.3 to 1.9.4
Bumps [github.com/pelletier/go-toml](https://github.com/pelletier/go-toml) from 1.9.3 to 1.9.4.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Commits](https://github.com/pelletier/go-toml/compare/v1.9.3...v1.9.4)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-15 18:09:44 +02:00
连修明andMárk Sági-Kazár eb876e1a15 update readme.md
```go
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
	fmt.Println("Config file changed:", e.Name)
})
```
this code will cause data race,  please check this question:
https://stackoverflow.com/questions/68915944/viper-dynamically-loading-config-file-has-data-race/68919971#68919971
2021-09-04 19:33:59 +02:00
dependabot[bot]andMárk Sági-Kazár 38e00eefc4 Bump github.com/fsnotify/fsnotify from 1.4.9 to 1.5.1
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.4.9 to 1.5.1.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.4.9...v1.5.1)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-04 19:28:57 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 5c0d079c4e ci: run builds on Go 1.17
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-08-24 23:23:02 +02:00
dependabot[bot]andMárk Sági-Kazár cf6565fd72 Bump github.com/spf13/cast from 1.4.0 to 1.4.1
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cast
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-17 01:41:55 +02:00
Márk Sági-KazárandGitHub a7cfd8b8e0 Fix bug report template 2021-07-30 03:38:09 +02:00
Márk Sági-KazárandGitHub 294bb31a4b Add Go version to bug report tempplate 2021-07-30 03:37:33 +02:00
dependabot[bot]andMárk Sági-Kazár f2053fabba Bump github.com/spf13/cast from 1.3.1 to 1.4.0
Bumps [github.com/spf13/cast](https://github.com/spf13/cast) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/spf13/cast/releases)
- [Commits](https://github.com/spf13/cast/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-28 03:20:43 +02:00
Mark Sagi-KazarandMárk Sági-Kazár ab4b05adc6 chore: add GitHub issue forms
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-07-14 04:02:54 +02:00
Márk Sági-Kazár e866eaa591 Update feedback_pull_request.yml 2021-07-12 19:53:25 +02:00
Márk Sági-Kazár bc5df54485 Update feedback_issue.yml 2021-07-12 19:53:25 +02:00
dependabot[bot]andMárk Sági-Kazár 409a7ba1d6 Bump actions/github-script from 3 to 4.0.2
Bumps [actions/github-script](https://github.com/actions/github-script) from 3 to 4.0.2.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v3...v4.0.2)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 19:53:25 +02:00
Márk Sági-Kazár 489df740ac Update dependabot.yml 2021-07-12 19:44:20 +02:00
Matthieu MORELandMárk Sági-Kazár 899b682f8c Create dependabot.yml 2021-07-12 19:44:20 +02:00
Mark Sagi-Kazar 030b739e60 docs: add section about decoding formats
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-06-25 14:49:58 +02:00
Mark Sagi-KazarandMárk Sági-Kazár a02f9864fa feat(encoding): Integrate the new encoding package into Viper
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-06-25 14:26:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 699d749768 feat(encoding): Add toml codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-06-25 14:26:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 6f15444771 feat(encoding): Add hcl codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-06-25 14:26:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 186266359b feat(encoding): Add json codec
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
2021-06-25 14:26:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár 0e854bf27b feat(encoding): Add yaml codec 2021-06-25 14:26:08 +02:00
Mark Sagi-KazarandMárk Sági-Kazár a00caae79f feat(encoding): Encoder/Decoder registry implementations 2021-06-25 14:26:08 +02:00
67 changed files with 4077 additions and 728 deletions
+1 -1
View File
@@ -11,5 +11,5 @@ trim_trailing_whitespace = true
[*.go]
indent_style = tab
[{Makefile, *.mk}]
[{Makefile,*.mk}]
indent_style = tab
+1 -1
View File
@@ -1,2 +1,2 @@
[*.yml]
[{*.yml,*.yaml}]
indent_size = 2
-34
View File
@@ -1,34 +0,0 @@
---
name: 🐛 Bug report
about: Report a bug to help us improve Viper
labels: kind/bug
---
<!--
Thank you for sending a bug report! Here are some tips:
1. Please fill out the template below to make it easier to debug your problem.
2. If you are not sure if it is a bug or not, you can ask in the Gophers slack channel `#viper`.
-->
**Expected behavior (what you expected to happen)**:
**Actual behavior (what actually happened)**:
**Repl.it link**:
<!-- you can use the following example: https://repl.it/@sagikazarmark/Viper-example -->
**Code reproducing the issue**:
<!-- just to make sure the content of the repl doesn't get lost -->
```go
```
<!-- include configuration file example if necessary -->
**Environment**:
- Viper version:
- Config source: <!-- flag, env, file, etc -->
- File format: <!-- JSON, YAML, TOML, etc -->
**Anything else we should know?**:
+119
View File
@@ -0,0 +1,119 @@
name: 🐛 Bug report
description: Report a bug to help us improve Viper
labels: [kind/bug]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a bug report!
Please fill out the template below to make it easier to debug your problem.
If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://github.com/spf13/viper/issues/new/choose).
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success.
required: true
- label: I am not looking for support or already pursued the available [support channels](https://github.com/spf13/viper/issues/new/choose) without success.
required: true
- label: I have checked the [troubleshooting guide](https://github.com/spf13/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success.
required: true
- type: input
attributes:
label: Viper Version
description: What version of Viper are you using?
placeholder: 1.8.1
validations:
required: true
- type: input
attributes:
label: Go Version
description: What version of Go are you using?
placeholder: "1.16"
validations:
required: true
- type: dropdown
attributes:
label: Config Source
description: What sources do you load configuration from?
options:
- Manual set
- Flags
- Environment variables
- Files
- Remove K/V stores
- Defaults
multiple: true
validations:
required: true
- type: dropdown
attributes:
label: Format
description: Which file formats do you use?
options:
- JSON
- YAML
- TOML
- Dotenv
- HCL
- Java properties
- INI
- Other (specify below)
multiple: true
- type: input
attributes:
label: Repl.it link
description: Complete example on Repl.it reproducing the issue. [Here](https://repl.it/@sagikazarmark/Viper-example) is an example you can use.
placeholder: https://repl.it/@sagikazarmark/Viper-example
- type: textarea
attributes:
label: Code reproducing the issue
description: Please provide a Repl.it link if possible.
render: go
placeholder: |
package main
import (
"github.com/spf13/viper"
)
func main() {
v := viper.New()
// ...
var config Config
err = v.Unmarshal(&config)
if err != nil {
panic(err)
}
}
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: A clear description of what actually happens.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior if it is not self-explanatory.
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
- type: textarea
attributes:
label: Additional Information
description: Links? References? Anything that will give us more context about the issue that you are encountering!
-21
View File
@@ -1,21 +0,0 @@
---
name: 🚀 Feature request
about: Suggest an idea for Viper
labels: kind/enhancement
---
<!--
Thank you for sending a feature request!
Please describe what you would like to change/add and why in detail by filling out the template below.
-->
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like to see**
<!-- A clear and concise description of what would you like to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
@@ -0,0 +1,39 @@
name: 🎉 Feature request
description: Suggest an idea for Viper
labels: [kind/enhancement]
body:
- type: markdown
attributes:
value: |
Thank you for submitting a feature request!
Please describe what you would like to change/add and why in detail by filling out the template below.
If you are not sure if your request fits into Viper, you can contact us via the available [support channels](https://github.com/spf13/viper/issues/new/choose).
- type: checkboxes
attributes:
label: Preflight Checklist
description: Please ensure you've completed all of the following.
options:
- label: I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success.
required: true
- type: textarea
attributes:
label: Problem Description
description: A clear and concise description of the problem you are seeking to solve with this feature request.
validations:
required: true
- type: textarea
attributes:
label: Proposed Solution
description: A clear and concise description of what would you like to happen.
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
attributes:
label: Additional Information
description: Add any other context about the problem here.
+16
View File
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
labels:
- area/dependencies
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
labels:
- area/dependencies
schedule:
interval: daily
+30
View File
@@ -0,0 +1,30 @@
changelog:
exclude:
labels:
- release-note/ignore
categories:
- title: Exciting New Features 🎉
labels:
- kind/feature
- release-note/new-feature
- title: Enhancements 🚀
labels:
- kind/enhancement
- release-note/enhancement
- title: Bug Fixes 🐛
labels:
- kind/bug
- release-note/bug-fix
- title: Breaking Changes 🛠
labels:
- release-note/breaking-change
- title: Deprecations ❌
labels:
- release-note/deprecation
- title: Dependency Updates ⬆️
labels:
- area/dependencies
- release-note/dependency-update
- title: Other Changes
labels:
- "*"
+18
View File
@@ -0,0 +1,18 @@
name: PR Checks
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
jobs:
release-label:
name: Release note label
runs-on: ubuntu-latest
steps:
- name: Check minimum labels
uses: mheap/github-action-required-labels@v2
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"
+86
View File
@@ -0,0 +1,86 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: js
goarch: wasm
- goos: aix
goarch: ppc64
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Checkout code
uses: actions/checkout@v3
- name: Build
run: go build .
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
# Fail fast is disabled because there are Go version specific features and tests
# that should be able to fail independently.
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.16', '1.17', '1.18', '1.19']
tags: ['', 'viper_yaml2', 'viper_toml1']
env:
GOFLAGS: -mod=readonly
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v3
- name: Test
run: go test -race -tags '${{ matrix.tags }}' -v ./...
if: runner.os != 'Windows'
- name: Test (without race detector)
run: go test -tags '${{ matrix.tags }}' -v ./...
if: runner.os == 'Windows'
lint:
name: Lint
runs-on: ubuntu-latest
env:
GOFLAGS: -mod=readonly
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Checkout code
uses: actions/checkout@v3
- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.49
-35
View File
@@ -1,35 +0,0 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
go: ['1.14', '1.15', '1.16']
env:
VERBOSE: 1
GOFLAGS: -mod=readonly
GOPROXY: https://proxy.golang.org
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v2
- name: Lint
run: make lint
- name: Test
run: make test
+72
View File
@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '22 16 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
@@ -6,11 +6,11 @@ jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
- uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.issues.createComment({
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
@@ -6,11 +6,11 @@ jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
- uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.issues.createComment({
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
-26
View File
@@ -1,26 +0,0 @@
name: WASM
on:
push:
branches:
- master
pull_request:
jobs:
build:
name: Build
runs-on: ubuntu-latest
env:
GOFLAGS: -mod=readonly
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: Checkout code
uses: actions/checkout@v2
- name: Ensure Viper compiles for WASM
run: GOOS=js GOARCH=wasm go build .
+5 -2
View File
@@ -3,7 +3,10 @@ run:
linters-settings:
gci:
local-prefixes: github.com/spf13/viper
sections:
- standard
- default
- prefix(github.com/spf13/viper)
golint:
min-confidence: 0
goimports:
@@ -20,7 +23,6 @@ linters:
- exhaustive
- exportloopref
- gci
- goconst
- gofmt
- gofumpt
- goimports
@@ -62,6 +64,7 @@ linters:
# - gochecknoglobals
# - gochecknoinits
# - gocognit
# - goconst
# - gocritic
# - gocyclo
# - godot
+3 -3
View File
@@ -15,8 +15,8 @@ TEST_FORMAT = short-verbose
endif
# Dependency versions
GOTESTSUM_VERSION = 1.6.4
GOLANGCI_VERSION = 1.40.1
GOTESTSUM_VERSION = 1.8.0
GOLANGCI_VERSION = 1.49.0
# Add the ability to override some variables
# Use with care
@@ -48,7 +48,7 @@ bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
bin/golangci-lint-${GOLANGCI_VERSION}:
@mkdir -p bin
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}
@mv bin/golangci-lint "$@"
.PHONY: lint
+59 -43
View File
@@ -11,7 +11,7 @@
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/spf13/viper/CI?style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI)
[![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/viper?style=flat-square)](https://goreportcard.com/report/github.com/spf13/viper)
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.14-61CFDD.svg?style=flat-square)
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.16-61CFDD.svg?style=flat-square)
[![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/spf13/viper)](https://pkg.go.dev/mod/github.com/spf13/viper)
**Go configuration with fangs!**
@@ -119,7 +119,7 @@ viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search
viper.AddConfigPath(".") // optionally look for config in the working directory
err := viper.ReadInConfig() // Find and read the config file
if err != nil { // Handle errors reading the config file
panic(fmt.Errorf("Fatal error config file: %w \n", err))
panic(fmt.Errorf("fatal error config file: %w", err))
}
```
@@ -127,11 +127,11 @@ You can handle the specific case where no config file is found like this:
```go
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
} else {
// Config file was found but another error was produced
}
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
} else {
// Config file was found but another error was produced
}
}
// Config file found and successfully parsed
@@ -175,10 +175,10 @@ Optionally you can provide a function for Viper to run each time a change occurs
**Make sure you add all of the configPaths prior to calling `WatchConfig()`**
```go
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
viper.WatchConfig()
```
### Reading Config from io.Reader
@@ -354,7 +354,7 @@ func main() {
i := viper.GetInt("flagname") // retrieve value from viper
...
// ...
}
```
@@ -447,6 +447,13 @@ viper.SetConfigType("json") // because there is no file extension in a stream of
err := viper.ReadRemoteConfig()
```
#### etcd3
```go
viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()
```
#### Consul
You need to set a key to Consul key/value storage with JSON value containing your desired config.
For example, create a Consul key/value store key `MY_CONSUL_KEY` with value:
@@ -503,18 +510,18 @@ runtime_viper.Unmarshal(&runtime_conf)
// open a goroutine to watch remote changes forever
go func(){
for {
time.Sleep(time.Second * 5) // delay after each request
time.Sleep(time.Second * 5) // delay after each request
// currently, only tested with etcd support
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
// currently, only tested with etcd support
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}
// unmarshal new config into our runtime config struct. you can also use channel
// to implement a signal to notify the system of the changes
runtime_viper.Unmarshal(&runtime_conf)
// unmarshal new config into our runtime config struct. you can also use channel
// to implement a signal to notify the system of the changes
runtime_viper.Unmarshal(&runtime_conf)
}
}()
```
@@ -546,7 +553,7 @@ Example:
```go
viper.GetString("logfile") // case-insensitive Setting & Getting
if viper.GetBool("verbose") {
fmt.Println("verbose enabled")
fmt.Println("verbose enabled")
}
```
### Accessing nested keys
@@ -594,7 +601,7 @@ configuration level.
Viper can access array indices by using numbers in the path. For example:
```json
```jsonc
{
"host": {
"address": "localhost",
@@ -622,7 +629,7 @@ GetInt("host.ports.1") // returns 6029
Lastly, if there exists a key that matches the delimited key path, its value
will be returned instead. E.g.
```json
```jsonc
{
"datastore.metric.host": "0.0.0.0",
"host": {
@@ -669,7 +676,7 @@ So instead of doing that let's pass a Viper instance to the constructor that rep
```go
cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // Sub returns nil if the key cannot be found
panic("cache configuration not found")
panic("cache configuration not found")
}
cache1 := NewCache(cache1Config)
@@ -681,10 +688,10 @@ Internally, the `NewCache` function can address `max-items` and `item-size` keys
```go
func NewCache(v *Viper) *Cache {
return &Cache{
MaxItems: v.GetInt("max-items"),
ItemSize: v.GetInt("item-size"),
}
return &Cache{
MaxItems: v.GetInt("max-items"),
ItemSize: v.GetInt("item-size"),
}
}
```
@@ -726,18 +733,18 @@ you have to change the delimiter:
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
v.SetDefault("chart::values", map[string]interface{}{
"ingress": map[string]interface{}{
"annotations": map[string]interface{}{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
"ingress": map[string]interface{}{
"annotations": map[string]interface{}{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
})
type config struct {
Chart struct{
Values map[string]interface{}
}
Values map[string]interface{}
}
}
var C config
@@ -778,6 +785,15 @@ if err != nil {
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
### Decoding custom formats
A frequently requested feature for Viper is adding more value formats and decoders.
For example, parsing character (dot, comma, semicolon, etc) separated strings into slices.
This is already available in Viper using mapstructure decode hooks.
Read more about the details in [this blog post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/).
### Marshalling to string
You may need to marshal all the settings held in viper into a string rather than write them to a file.
@@ -785,17 +801,17 @@ You can use your favorite format's marshaller with the config returned by `AllSe
```go
import (
yaml "gopkg.in/yaml.v2"
// ...
yaml "gopkg.in/yaml.v2"
// ...
)
func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}
```
+9
View File
@@ -21,3 +21,12 @@ The solution is easy: switch to using Go Modules.
Please refer to the [wiki](https://github.com/golang/go/wiki/Modules) on how to do that.
**tl;dr* `export GO111MODULE=on`
## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file
This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740).
Potential solutions are:
1. Quoting values resolved as boolean
1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build)
+11
View File
@@ -0,0 +1,11 @@
//go:build viper_logger
// +build viper_logger
package viper
// WithLogger sets a custom logger.
func WithLogger(l Logger) Option {
return optionFunc(func(v *Viper) {
v.logger = l
})
}
+65
View File
@@ -0,0 +1,65 @@
//go:build go1.16 && finder
// +build go1.16,finder
package viper
import (
"errors"
"io/fs"
"path"
)
type finder struct {
paths []string
fileNames []string
extensions []string
withoutExtension bool
}
func (f finder) Find(fsys fs.FS) (string, error) {
for _, searchPath := range f.paths {
for _, fileName := range f.fileNames {
for _, extension := range f.extensions {
filePath := path.Join(searchPath, fileName+"."+extension)
ok, err := fileExists(fsys, filePath)
if err != nil {
return "", err
}
if ok {
return filePath, nil
}
}
if f.withoutExtension {
filePath := path.Join(searchPath, fileName)
ok, err := fileExists(fsys, filePath)
if err != nil {
return "", err
}
if ok {
return filePath, nil
}
}
}
}
return "", nil
}
func fileExists(fsys fs.FS, filePath string) (bool, error) {
fileInfo, err := fs.Stat(fsys, filePath)
if err == nil {
return !fileInfo.IsDir(), nil
}
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
return false, err
}
+100
View File
@@ -0,0 +1,100 @@
//go:build go1.16 && finder
// +build go1.16,finder
package viper
import (
"io/fs"
"testing"
"testing/fstest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFinder(t *testing.T) {
t.Parallel()
fsys := fstest.MapFS{
"home/user/.config": &fstest.MapFile{},
"home/user/config.json": &fstest.MapFile{},
"home/user/config.yaml": &fstest.MapFile{},
"home/user/data.json": &fstest.MapFile{},
"etc/config/.config": &fstest.MapFile{},
"etc/config/a_random_file.txt": &fstest.MapFile{},
"etc/config/config.json": &fstest.MapFile{},
"etc/config/config.yaml": &fstest.MapFile{},
"etc/config/config.xml": &fstest.MapFile{},
}
testCases := []struct {
name string
fsys func() fs.FS
finder finder
result string
}{
{
name: "find file",
fsys: func() fs.FS { return fsys },
finder: finder{
paths: []string{"etc/config"},
fileNames: []string{"config"},
extensions: []string{"json"},
},
result: "etc/config/config.json",
},
{
name: "file not found",
fsys: func() fs.FS { return fsys },
finder: finder{
paths: []string{"var/config"},
fileNames: []string{"config"},
extensions: []string{"json"},
},
result: "",
},
{
name: "empty search params",
fsys: func() fs.FS { return fsys },
finder: finder{},
result: "",
},
{
name: "precedence",
fsys: func() fs.FS { return fsys },
finder: finder{
paths: []string{"var/config", "home/user", "etc/config"},
fileNames: []string{"aconfig", "config"},
extensions: []string{"zml", "xml", "json"},
},
result: "home/user/config.json",
},
{
name: "without extension",
fsys: func() fs.FS { return fsys },
finder: finder{
paths: []string{"var/config", "home/user", "etc/config"},
fileNames: []string{".config"},
extensions: []string{"zml", "xml", "json"},
withoutExtension: true,
},
result: "home/user/.config",
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
fsys := testCase.fsys()
result, err := testCase.finder.Find(fsys)
require.NoError(t, err)
assert.Equal(t, testCase.result, result)
})
}
}
+67 -12
View File
@@ -1,21 +1,76 @@
module github.com/spf13/viper
go 1.12
go 1.17
require (
github.com/bketelsen/crypt v0.0.4
github.com/fsnotify/fsnotify v1.4.9
github.com/fsnotify/fsnotify v1.6.0
github.com/hashicorp/hcl v1.0.0
github.com/magiconair/properties v1.8.5
github.com/mitchellh/mapstructure v1.4.1
github.com/pelletier/go-toml v1.9.3
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/afero v1.6.0
github.com/spf13/cast v1.3.1
github.com/magiconair/properties v1.8.6
github.com/mitchellh/mapstructure v1.5.0
github.com/pelletier/go-toml v1.9.5
github.com/pelletier/go-toml/v2 v2.0.5
github.com/sagikazarmark/crypt v0.8.0
github.com/spf13/afero v1.9.2
github.com/spf13/cast v1.5.0
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/subosito/gotenv v1.2.0
gopkg.in/ini.v1 v1.62.0
github.com/stretchr/testify v1.8.1
github.com/subosito/gotenv v1.4.1
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cloud.google.com/go v0.104.0 // indirect
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
cloud.google.com/go/firestore v1.8.0 // indirect
github.com/armon/go-metrics v0.4.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/hashicorp/consul/api v1.15.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.2.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.9.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.5 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
go.etcd.io/etcd/client/v2 v2.305.5 // indirect
go.etcd.io/etcd/client/v3 v3.5.5 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.102.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
+616 -102
View File
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
package encoding
import (
"sync"
)
// Decoder decodes the contents of b into v.
// It's primarily used for decoding contents of a file into a map[string]interface{}.
type Decoder interface {
Decode(b []byte, v map[string]interface{}) error
}
const (
// ErrDecoderNotFound is returned when there is no decoder registered for a format.
ErrDecoderNotFound = encodingError("decoder not found for this format")
// ErrDecoderFormatAlreadyRegistered is returned when an decoder is already registered for a format.
ErrDecoderFormatAlreadyRegistered = encodingError("decoder already registered for this format")
)
// DecoderRegistry can choose an appropriate Decoder based on the provided format.
type DecoderRegistry struct {
decoders map[string]Decoder
mu sync.RWMutex
}
// NewDecoderRegistry returns a new, initialized DecoderRegistry.
func NewDecoderRegistry() *DecoderRegistry {
return &DecoderRegistry{
decoders: make(map[string]Decoder),
}
}
// RegisterDecoder registers a Decoder for a format.
// Registering a Decoder for an already existing format is not supported.
func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
e.mu.Lock()
defer e.mu.Unlock()
if _, ok := e.decoders[format]; ok {
return ErrDecoderFormatAlreadyRegistered
}
e.decoders[format] = enc
return nil
}
// Decode calls the underlying Decoder based on the format.
func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
e.mu.RLock()
decoder, ok := e.decoders[format]
e.mu.RUnlock()
if !ok {
return ErrDecoderNotFound
}
return decoder.Decode(b, v)
}
+81
View File
@@ -0,0 +1,81 @@
package encoding
import (
"reflect"
"testing"
)
type decoder struct {
v map[string]interface{}
}
func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
for key, value := range d.v {
v[key] = value
}
return nil
}
func TestDecoderRegistry_RegisterDecoder(t *testing.T) {
t.Run("OK", func(t *testing.T) {
registry := NewDecoderRegistry()
err := registry.RegisterDecoder("myformat", decoder{})
if err != nil {
t.Fatal(err)
}
})
t.Run("AlreadyRegistered", func(t *testing.T) {
registry := NewDecoderRegistry()
err := registry.RegisterDecoder("myformat", decoder{})
if err != nil {
t.Fatal(err)
}
err = registry.RegisterDecoder("myformat", decoder{})
if err != ErrDecoderFormatAlreadyRegistered {
t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
}
})
}
func TestDecoderRegistry_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
registry := NewDecoderRegistry()
decoder := decoder{
v: map[string]interface{}{
"key": "value",
},
}
err := registry.RegisterDecoder("myformat", decoder)
if err != nil {
t.Fatal(err)
}
v := map[string]interface{}{}
err = registry.Decode("myformat", []byte("key: value"), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(decoder.v, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
}
})
t.Run("DecoderNotFound", func(t *testing.T) {
registry := NewDecoderRegistry()
v := map[string]interface{}{}
err := registry.Decode("myformat", nil, v)
if err != ErrDecoderNotFound {
t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
}
})
}
+61
View File
@@ -0,0 +1,61 @@
package dotenv
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/subosito/gotenv"
)
const keyDelimiter = "_"
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables
// (commonly called as dotenv format).
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
flattened := map[string]interface{}{}
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
keys := make([]string, 0, len(flattened))
for key := range flattened {
keys = append(keys, key)
}
sort.Strings(keys)
var buf bytes.Buffer
for _, key := range keys {
_, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key]))
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
var buf bytes.Buffer
_, err := buf.Write(b)
if err != nil {
return err
}
env, err := gotenv.StrictParse(&buf)
if err != nil {
return err
}
for key, value := range env {
v[key] = value
}
return nil
}
+63
View File
@@ -0,0 +1,63 @@
package dotenv
import (
"reflect"
"testing"
)
// original form of the data
const original = `# key-value pair
KEY=value
`
// encoded form of the data
const encoded = `KEY=value
`
// Viper's internal representation
var data = map[string]interface{}{
"KEY": "value",
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+41
View File
@@ -0,0 +1,41 @@
package dotenv
import (
"strings"
"github.com/spf13/cast"
)
// flattenAndMergeMap recursively flattens the given map into a new map
// Code is based on the function with the same name in tha main package.
// TODO: move it to a common place
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
if shadow != nil && prefix != "" && shadow[prefix] != nil {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]interface{})
}
var m2 map[string]interface{}
if prefix != "" {
prefix += delimiter
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = val
continue
}
// recursively merge to shadow map
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
}
return shadow
}
+60
View File
@@ -0,0 +1,60 @@
package encoding
import (
"sync"
)
// Encoder encodes the contents of v into a byte representation.
// It's primarily used for encoding a map[string]interface{} into a file format.
type Encoder interface {
Encode(v map[string]interface{}) ([]byte, error)
}
const (
// ErrEncoderNotFound is returned when there is no encoder registered for a format.
ErrEncoderNotFound = encodingError("encoder not found for this format")
// ErrEncoderFormatAlreadyRegistered is returned when an encoder is already registered for a format.
ErrEncoderFormatAlreadyRegistered = encodingError("encoder already registered for this format")
)
// EncoderRegistry can choose an appropriate Encoder based on the provided format.
type EncoderRegistry struct {
encoders map[string]Encoder
mu sync.RWMutex
}
// NewEncoderRegistry returns a new, initialized EncoderRegistry.
func NewEncoderRegistry() *EncoderRegistry {
return &EncoderRegistry{
encoders: make(map[string]Encoder),
}
}
// RegisterEncoder registers an Encoder for a format.
// Registering a Encoder for an already existing format is not supported.
func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
e.mu.Lock()
defer e.mu.Unlock()
if _, ok := e.encoders[format]; ok {
return ErrEncoderFormatAlreadyRegistered
}
e.encoders[format] = enc
return nil
}
func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
e.mu.RLock()
encoder, ok := e.encoders[format]
e.mu.RUnlock()
if !ok {
return nil, ErrEncoderNotFound
}
return encoder.Encode(v)
}
+70
View File
@@ -0,0 +1,70 @@
package encoding
import (
"testing"
)
type encoder struct {
b []byte
}
func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
return e.b, nil
}
func TestEncoderRegistry_RegisterEncoder(t *testing.T) {
t.Run("OK", func(t *testing.T) {
registry := NewEncoderRegistry()
err := registry.RegisterEncoder("myformat", encoder{})
if err != nil {
t.Fatal(err)
}
})
t.Run("AlreadyRegistered", func(t *testing.T) {
registry := NewEncoderRegistry()
err := registry.RegisterEncoder("myformat", encoder{})
if err != nil {
t.Fatal(err)
}
err = registry.RegisterEncoder("myformat", encoder{})
if err != ErrEncoderFormatAlreadyRegistered {
t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err)
}
})
}
func TestEncoderRegistry_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
registry := NewEncoderRegistry()
encoder := encoder{
b: []byte("key: value"),
}
err := registry.RegisterEncoder("myformat", encoder)
if err != nil {
t.Fatal(err)
}
b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
if err != nil {
t.Fatal(err)
}
if string(b) != "key: value" {
t.Fatalf("expected 'key: value', got: %#v", string(b))
}
})
t.Run("EncoderNotFound", func(t *testing.T) {
registry := NewEncoderRegistry()
_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
if err != ErrEncoderNotFound {
t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
}
})
}
+7
View File
@@ -0,0 +1,7 @@
package encoding
type encodingError string
func (e encodingError) Error() string {
return string(e)
}
+40
View File
@@ -0,0 +1,40 @@
package hcl
import (
"bytes"
"encoding/json"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/printer"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding.
// TODO: add printer config to the codec?
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
// TODO: use printer.Format? Is the trailing newline an issue?
ast, err := hcl.Parse(string(b))
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = printer.Fprint(&buf, ast.Node)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
return hcl.Unmarshal(b, &v)
}
+140
View File
@@ -0,0 +1,140 @@
package hcl
import (
"reflect"
"testing"
)
// original form of the data
const original = `# key-value pair
"key" = "value"
// list
"list" = ["item1", "item2", "item3"]
/* map */
"map" = {
"key" = "value"
}
/*
nested map
*/
"nested_map" "map" {
"key" = "value"
"list" = ["item1", "item2", "item3"]
}`
// encoded form of the data
const encoded = `"key" = "value"
"list" = ["item1", "item2", "item3"]
"map" = {
"key" = "value"
}
"nested_map" "map" {
"key" = "value"
"list" = ["item1", "item2", "item3"]
}`
// decoded form of the data
//
// in case of HCL it's slightly different from Viper's internal representation
// (eg. map is decoded into a list of maps)
var decoded = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": []map[string]interface{}{
{
"key": "value",
},
},
"nested_map": []map[string]interface{}{
{
"map": []map[string]interface{}{
{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
},
},
}
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(decoded, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+99
View File
@@ -0,0 +1,99 @@
package ini
import (
"bytes"
"sort"
"strings"
"github.com/spf13/cast"
"gopkg.in/ini.v1"
)
// LoadOptions contains all customized options used for load data source(s).
// This type is added here for convenience: this way consumers can import a single package called "ini".
type LoadOptions = ini.LoadOptions
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding.
type Codec struct {
KeyDelimiter string
LoadOptions LoadOptions
}
func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
cfg := ini.Empty()
ini.PrettyFormat = false
flattened := map[string]interface{}{}
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
keys := make([]string, 0, len(flattened))
for key := range flattened {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
sectionName, keyName := "", key
lastSep := strings.LastIndex(key, ".")
if lastSep != -1 {
sectionName = key[:(lastSep)]
keyName = key[(lastSep + 1):]
}
// TODO: is this a good idea?
if sectionName == "default" {
sectionName = ""
}
cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key]))
}
var buf bytes.Buffer
_, err := cfg.WriteTo(&buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (c Codec) Decode(b []byte, v map[string]interface{}) error {
cfg := ini.Empty(c.LoadOptions)
err := cfg.Append(b)
if err != nil {
return err
}
sections := cfg.Sections()
for i := 0; i < len(sections); i++ {
section := sections[i]
keys := section.Keys()
for j := 0; j < len(keys); j++ {
key := keys[j]
value := cfg.Section(section.Name()).Key(key.Name()).String()
deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter()))
// set innermost value
deepestMap[key.Name()] = value
}
}
return nil
}
func (c Codec) keyDelimiter() string {
if c.KeyDelimiter == "" {
return "."
}
return c.KeyDelimiter
}
+111
View File
@@ -0,0 +1,111 @@
package ini
import (
"reflect"
"testing"
)
// original form of the data
const original = `; key-value pair
key=value ; key-value pair
# map
[map] # map
key=%(key)s
`
// encoded form of the data
const encoded = `key=value
[map]
key=value
`
// decoded form of the data
//
// in case of INI it's slightly different from Viper's internal representation
// (eg. top level keys land in a section called default)
var decoded = map[string]interface{}{
"DEFAULT": map[string]interface{}{
"key": "value",
},
"map": map[string]interface{}{
"key": "value",
},
}
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"map": map[string]interface{}{
"key": "value",
},
}
func TestCodec_Encode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
})
t.Run("Default", func(t *testing.T) {
codec := Codec{}
data := map[string]interface{}{
"default": map[string]interface{}{
"key": "value",
},
"map": map[string]interface{}{
"key": "value",
},
}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
})
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(decoded, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+74
View File
@@ -0,0 +1,74 @@
package ini
import (
"strings"
"github.com/spf13/cast"
)
// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
// deepSearch scans deep maps, following the key indexes listed in the
// sequence "path".
// The last value is expected to be another map, and is returned.
//
// In case intermediate keys do not exist, or map to a non-map value,
// a new map is created and inserted, and the search continues from there:
// the initial map "m" may be modified!
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
for _, k := range path {
m2, ok := m[k]
if !ok {
// intermediate key does not exist
// => create it and continue from there
m3 := make(map[string]interface{})
m[k] = m3
m = m3
continue
}
m3, ok := m2.(map[string]interface{})
if !ok {
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]interface{})
m[k] = m3
}
// continue search from here
m = m3
}
return m
}
// flattenAndMergeMap recursively flattens the given map into a new map
// Code is based on the function with the same name in tha main package.
// TODO: move it to a common place
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
if shadow != nil && prefix != "" && shadow[prefix] != nil {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]interface{})
}
var m2 map[string]interface{}
if prefix != "" {
prefix += delimiter
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = val
continue
}
// recursively merge to shadow map
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
}
return shadow
}
+86
View File
@@ -0,0 +1,86 @@
package javaproperties
import (
"bytes"
"sort"
"strings"
"github.com/magiconair/properties"
"github.com/spf13/cast"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
type Codec struct {
KeyDelimiter string
// Store read properties on the object so that we can write back in order with comments.
// This will only be used if the configuration read is a properties file.
// TODO: drop this feature in v2
// TODO: make use of the global properties object optional
Properties *properties.Properties
}
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
if c.Properties == nil {
c.Properties = properties.NewProperties()
}
flattened := map[string]interface{}{}
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
keys := make([]string, 0, len(flattened))
for key := range flattened {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
_, _, err := c.Properties.Set(key, cast.ToString(flattened[key]))
if err != nil {
return nil, err
}
}
var buf bytes.Buffer
_, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
var err error
c.Properties, err = properties.Load(b, properties.UTF8)
if err != nil {
return err
}
for _, key := range c.Properties.Keys() {
// ignore existence check: we know it's there
value, _ := c.Properties.Get(key)
// recursively build nested maps
path := strings.Split(key, c.keyDelimiter())
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(v, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
return nil
}
func (c Codec) keyDelimiter() string {
if c.KeyDelimiter == "" {
return "."
}
return c.KeyDelimiter
}
@@ -0,0 +1,89 @@
package javaproperties
import (
"reflect"
"testing"
)
// original form of the data
const original = `#key-value pair
key = value
map.key = value
`
// encoded form of the data
const encoded = `key = value
map.key = value
`
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"map": map[string]interface{}{
"key": "value",
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
}
})
t.Run("InvalidData", func(t *testing.T) {
t.Skip("TODO: needs invalid data example")
codec := Codec{}
v := map[string]interface{}{}
codec.Decode([]byte(``), v)
if len(v) > 0 {
t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v)
}
})
}
func TestCodec_DecodeEncode(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if original != string(b) {
t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original)
}
}
@@ -0,0 +1,74 @@
package javaproperties
import (
"strings"
"github.com/spf13/cast"
)
// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
// deepSearch scans deep maps, following the key indexes listed in the
// sequence "path".
// The last value is expected to be another map, and is returned.
//
// In case intermediate keys do not exist, or map to a non-map value,
// a new map is created and inserted, and the search continues from there:
// the initial map "m" may be modified!
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
for _, k := range path {
m2, ok := m[k]
if !ok {
// intermediate key does not exist
// => create it and continue from there
m3 := make(map[string]interface{})
m[k] = m3
m = m3
continue
}
m3, ok := m2.(map[string]interface{})
if !ok {
// intermediate key is a value
// => replace with a new map
m3 = make(map[string]interface{})
m[k] = m3
}
// continue search from here
m = m3
}
return m
}
// flattenAndMergeMap recursively flattens the given map into a new map
// Code is based on the function with the same name in tha main package.
// TODO: move it to a common place
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
if shadow != nil && prefix != "" && shadow[prefix] != nil {
// prefix is shadowed => nothing more to flatten
return shadow
}
if shadow == nil {
shadow = make(map[string]interface{})
}
var m2 map[string]interface{}
if prefix != "" {
prefix += delimiter
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = cast.ToStringMap(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = val
continue
}
// recursively merge to shadow map
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
}
return shadow
}
+17
View File
@@ -0,0 +1,17 @@
package json
import (
"encoding/json"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
// TODO: expose prefix and indent in the Codec as setting?
return json.MarshalIndent(v, "", " ")
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
return json.Unmarshal(b, &v)
}
+95
View File
@@ -0,0 +1,95 @@
package json
import (
"reflect"
"testing"
)
// encoded form of the data
const encoded = `{
"key": "value",
"list": [
"item1",
"item2",
"item3"
],
"map": {
"key": "value"
},
"nested_map": {
"map": {
"key": "value",
"list": [
"item1",
"item2",
"item3"
]
}
}
}`
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(encoded), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+39
View File
@@ -0,0 +1,39 @@
//go:build viper_toml1
// +build viper_toml1
package toml
import (
"github.com/pelletier/go-toml"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
t, err := toml.TreeFromMap(v)
if err != nil {
return nil, err
}
s, err := t.ToTomlString()
if err != nil {
return nil, err
}
return []byte(s), nil
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
tree, err := toml.LoadBytes(b)
if err != nil {
return err
}
tmap := tree.ToMap()
for key, value := range tmap {
v[key] = value
}
return nil
}
+19
View File
@@ -0,0 +1,19 @@
//go:build !viper_toml1
// +build !viper_toml1
package toml
import (
"github.com/pelletier/go-toml/v2"
)
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
return toml.Marshal(v)
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
return toml.Unmarshal(b, &v)
}
+108
View File
@@ -0,0 +1,108 @@
//go:build !viper_toml1
// +build !viper_toml1
package toml
import (
"reflect"
"testing"
)
// original form of the data
const original = `# key-value pair
key = "value"
list = ["item1", "item2", "item3"]
[map]
key = "value"
# nested
# map
[nested_map]
[nested_map.map]
key = "value"
list = [
"item1",
"item2",
"item3",
]
`
// encoded form of the data
const encoded = `key = 'value'
list = ['item1', 'item2', 'item3']
[map]
key = 'value'
[nested_map]
[nested_map.map]
key = 'value'
list = ['item1', 'item2', 'item3']
`
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+109
View File
@@ -0,0 +1,109 @@
//go:build viper_toml1
// +build viper_toml1
package toml
import (
"reflect"
"testing"
)
// original form of the data
const original = `# key-value pair
key = "value"
list = ["item1", "item2", "item3"]
[map]
key = "value"
# nested
# map
[nested_map]
[nested_map.map]
key = "value"
list = [
"item1",
"item2",
"item3",
]
`
// encoded form of the data
const encoded = `key = "value"
list = ["item1", "item2", "item3"]
[map]
key = "value"
[nested_map]
[nested_map.map]
key = "value"
list = ["item1", "item2", "item3"]
`
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(data, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+14
View File
@@ -0,0 +1,14 @@
package yaml
// import "gopkg.in/yaml.v2"
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
type Codec struct{}
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
func (Codec) Decode(b []byte, v map[string]interface{}) error {
return yaml.Unmarshal(b, &v)
}
+49
View File
@@ -0,0 +1,49 @@
package yaml
import (
"reflect"
"testing"
)
func TestCodec_Encode(t *testing.T) {
codec := Codec{}
b, err := codec.Encode(data)
if err != nil {
t.Fatal(err)
}
if encoded != string(b) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
}
}
func TestCodec_Decode(t *testing.T) {
t.Run("OK", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(original), v)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(decoded, v) {
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
}
})
t.Run("InvalidData", func(t *testing.T) {
codec := Codec{}
v := map[string]interface{}{}
err := codec.Decode([]byte(`invalid data`), v)
if err == nil {
t.Fatal("expected decoding to fail")
}
t.Logf("decoding failed as expected: %s", err)
})
}
+14
View File
@@ -0,0 +1,14 @@
//go:build viper_yaml2
// +build viper_yaml2
package yaml
import yamlv2 "gopkg.in/yaml.v2"
var yaml = struct {
Marshal func(in interface{}) (out []byte, err error)
Unmarshal func(in []byte, out interface{}) (err error)
}{
Marshal: yamlv2.Marshal,
Unmarshal: yamlv2.Unmarshal,
}
+91
View File
@@ -0,0 +1,91 @@
//go:build viper_yaml2
// +build viper_yaml2
package yaml
// original form of the data
const original = `# key-value pair
key: value
list:
- item1
- item2
- item3
map:
key: value
# nested
# map
nested_map:
map:
key: value
list:
- item1
- item2
- item3
`
// encoded form of the data
const encoded = `key: value
list:
- item1
- item2
- item3
map:
key: value
nested_map:
map:
key: value
list:
- item1
- item2
- item3
`
// decoded form of the data
//
// in case of YAML it's slightly different from Viper's internal representation
// (eg. map is decoded into a map with interface key)
var decoded = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[interface{}]interface{}{
"key": "value",
},
"nested_map": map[interface{}]interface{}{
"map": map[interface{}]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
+14
View File
@@ -0,0 +1,14 @@
//go:build !viper_yaml2
// +build !viper_yaml2
package yaml
import yamlv3 "gopkg.in/yaml.v3"
var yaml = struct {
Marshal func(in interface{}) (out []byte, err error)
Unmarshal func(in []byte, out interface{}) (err error)
}{
Marshal: yamlv3.Marshal,
Unmarshal: yamlv3.Unmarshal,
}
+91
View File
@@ -0,0 +1,91 @@
//go:build !viper_yaml2
// +build !viper_yaml2
package yaml
// original form of the data
const original = `# key-value pair
key: value
list:
- item1
- item2
- item3
map:
key: value
# nested
# map
nested_map:
map:
key: value
list:
- item1
- item2
- item3
`
// encoded form of the data
const encoded = `key: value
list:
- item1
- item2
- item3
map:
key: value
nested_map:
map:
key: value
list:
- item1
- item2
- item3
`
// decoded form of the data
//
// in case of YAML it's slightly different from Viper's internal representation
// (eg. map is decoded into a map with interface key)
var decoded = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
// Viper's internal representation
var data = map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
"map": map[string]interface{}{
"key": "value",
},
"nested_map": map[string]interface{}{
"map": map[string]interface{}{
"key": "value",
"list": []interface{}{
"item1",
"item2",
"item3",
},
},
},
}
@@ -1,3 +1,6 @@
//go:build !go1.17
// +build !go1.17
package testutil
import (
@@ -18,11 +21,6 @@ func Setenv(t *testing.T, name, val string) {
setenv(t, name, val, true)
}
// Unsetenv unsets an environment variable for the duration of a test.
func Unsetenv(t *testing.T, name string) {
setenv(t, name, "", false)
}
// setenv sets or unsets an environment variable to a temporary value for the
// duration of the test
func setenv(t *testing.T, name, val string, valOK bool) {
+18
View File
@@ -0,0 +1,18 @@
//go:build go1.17
// +build go1.17
package testutil
import (
"testing"
)
// Setenv sets an environment variable to a temporary value for the
// duration of the test.
//
// This shim can be removed once support for Go <1.17 is dropped.
func Setenv(t *testing.T, name, val string) {
t.Helper()
t.Setenv(name, val)
}
+18
View File
@@ -0,0 +1,18 @@
package testutil
import (
"path/filepath"
"testing"
)
// AbsFilePath calls filepath.Abs on path.
func AbsFilePath(t *testing.T, path string) string {
t.Helper()
s, err := filepath.Abs(path)
if err != nil {
t.Fatal(err)
}
return s
}
+77
View File
@@ -0,0 +1,77 @@
package viper
import (
"fmt"
jww "github.com/spf13/jwalterweatherman"
)
// Logger is a unified interface for various logging use cases and practices, including:
// - leveled logging
// - structured logging
type Logger interface {
// Trace logs a Trace event.
//
// Even more fine-grained information than Debug events.
// Loggers not supporting this level should fall back to Debug.
Trace(msg string, keyvals ...interface{})
// Debug logs a Debug event.
//
// A verbose series of information events.
// They are useful when debugging the system.
Debug(msg string, keyvals ...interface{})
// Info logs an Info event.
//
// General information about what's happening inside the system.
Info(msg string, keyvals ...interface{})
// Warn logs a Warn(ing) event.
//
// Non-critical events that should be looked at.
Warn(msg string, keyvals ...interface{})
// Error logs an Error event.
//
// Critical events that require immediate attention.
// Loggers commonly provide Fatal and Panic levels above Error level,
// but exiting and panicing is out of scope for a logging library.
Error(msg string, keyvals ...interface{})
}
type jwwLogger struct{}
func (jwwLogger) Trace(msg string, keyvals ...interface{}) {
jww.TRACE.Printf(jwwLogMessage(msg, keyvals...))
}
func (jwwLogger) Debug(msg string, keyvals ...interface{}) {
jww.DEBUG.Printf(jwwLogMessage(msg, keyvals...))
}
func (jwwLogger) Info(msg string, keyvals ...interface{}) {
jww.INFO.Printf(jwwLogMessage(msg, keyvals...))
}
func (jwwLogger) Warn(msg string, keyvals ...interface{}) {
jww.WARN.Printf(jwwLogMessage(msg, keyvals...))
}
func (jwwLogger) Error(msg string, keyvals ...interface{}) {
jww.ERROR.Printf(jwwLogMessage(msg, keyvals...))
}
func jwwLogMessage(msg string, keyvals ...interface{}) string {
out := msg
if len(keyvals) > 0 && len(keyvals)%2 == 1 {
keyvals = append(keyvals, nil)
}
for i := 0; i <= len(keyvals)-2; i += 2 {
out = fmt.Sprintf("%s %v=%v", out, keyvals[i], keyvals[i+1])
}
return out
}
+5 -1
View File
@@ -11,7 +11,7 @@ import (
"io"
"os"
crypt "github.com/bketelsen/crypt/config"
crypt "github.com/sagikazarmark/crypt/config"
"github.com/spf13/viper"
)
@@ -85,6 +85,8 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
switch rp.Provider() {
case "etcd":
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
case "etcd3":
cm, err = crypt.NewEtcdV3ConfigManager([]string{rp.Endpoint()}, kr)
case "firestore":
cm, err = crypt.NewFirestoreConfigManager([]string{rp.Endpoint()}, kr)
default:
@@ -94,6 +96,8 @@ func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
switch rp.Provider() {
case "etcd":
cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
case "etcd3":
cm, err = crypt.NewStandardEtcdV3ConfigManager([]string{rp.Endpoint()})
case "firestore":
cm, err = crypt.NewStandardFirestoreConfigManager([]string{rp.Endpoint()})
default:
+28 -41
View File
@@ -18,9 +18,7 @@ import (
"strings"
"unicode"
"github.com/spf13/afero"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
)
// ConfigParseError denotes failing to parse configuration file.
@@ -66,18 +64,25 @@ func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
return nm
}
func insensitiviseVal(val interface{}) interface{} {
switch val.(type) {
case map[interface{}]interface{}:
// nested map: cast and recursively insensitivise
val = cast.ToStringMap(val)
insensitiviseMap(val.(map[string]interface{}))
case map[string]interface{}:
// nested map: recursively insensitivise
insensitiviseMap(val.(map[string]interface{}))
case []interface{}:
// nested array: recursively insensitivise
insensitiveArray(val.([]interface{}))
}
return val
}
func insensitiviseMap(m map[string]interface{}) {
for key, val := range m {
switch val.(type) {
case map[interface{}]interface{}:
// nested map: cast and recursively insensitivise
val = cast.ToStringMap(val)
insensitiviseMap(val.(map[string]interface{}))
case map[string]interface{}:
// nested map: recursively insensitivise
insensitiviseMap(val.(map[string]interface{}))
}
val = insensitiviseVal(val)
lower := strings.ToLower(key)
if key != lower {
// remove old key (not lower-cased)
@@ -88,26 +93,20 @@ func insensitiviseMap(m map[string]interface{}) {
}
}
func absPathify(inPath string) string {
jww.INFO.Println("Trying to resolve absolute path to", inPath)
func insensitiveArray(a []interface{}) {
for i, val := range a {
a[i] = insensitiviseVal(val)
}
}
func absPathify(logger Logger, inPath string) string {
logger.Info("trying to resolve absolute path", "path", inPath)
if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
inPath = userHomeDir() + inPath[5:]
}
if strings.HasPrefix(inPath, "$") {
end := strings.Index(inPath, string(os.PathSeparator))
var value, suffix string
if end == -1 {
value = os.Getenv(inPath[1:])
} else {
value = os.Getenv(inPath[1:end])
suffix = inPath[end:]
}
inPath = value + suffix
}
inPath = os.ExpandEnv(inPath)
if filepath.IsAbs(inPath) {
return filepath.Clean(inPath)
@@ -118,21 +117,9 @@ func absPathify(inPath string) string {
return filepath.Clean(p)
}
jww.ERROR.Println("Couldn't discover absolute path")
jww.ERROR.Println(err)
return ""
}
logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
// Check if file Exists
func exists(fs afero.Fs, path string) (bool, error) {
stat, err := fs.Stat(path)
if err == nil {
return !stat.IsDir(), nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
return ""
}
func stringInSlice(a string, list []string) bool {
+3 -1
View File
@@ -58,6 +58,8 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
}
func TestAbsPathify(t *testing.T) {
skipWindows(t)
home := userHomeDir()
homer := filepath.Join(home, "homer")
wd, _ := os.Getwd()
@@ -85,7 +87,7 @@ func TestAbsPathify(t *testing.T) {
}
for _, test := range tests {
got := absPathify(test.input)
got := absPathify(jwwLogger{}, test.input)
if got != test.output {
t.Errorf("Got %v\nexpected\n%q", got, test.output)
}
+253 -273
View File
@@ -22,7 +22,6 @@ package viper
import (
"bytes"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
@@ -36,18 +35,19 @@ import (
"time"
"github.com/fsnotify/fsnotify"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/printer"
"github.com/magiconair/properties"
"github.com/mitchellh/mapstructure"
"github.com/pelletier/go-toml"
"github.com/spf13/afero"
"github.com/spf13/cast"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
"github.com/subosito/gotenv"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
"github.com/spf13/viper/internal/encoding"
"github.com/spf13/viper/internal/encoding/dotenv"
"github.com/spf13/viper/internal/encoding/hcl"
"github.com/spf13/viper/internal/encoding/ini"
"github.com/spf13/viper/internal/encoding/javaproperties"
"github.com/spf13/viper/internal/encoding/json"
"github.com/spf13/viper/internal/encoding/toml"
"github.com/spf13/viper/internal/encoding/yaml"
)
// ConfigMarshalError happens when failing to marshal the configuration.
@@ -132,10 +132,10 @@ type DecoderConfigOption func(*mapstructure.DecoderConfig)
// DecodeHook returns a DecoderConfigOption which overrides the default
// DecoderConfig.DecodeHook value, the default is:
//
// mapstructure.ComposeDecodeHookFunc(
// mapstructure.StringToTimeDurationHookFunc(),
// mapstructure.StringToSliceHookFunc(","),
// )
// mapstructure.ComposeDecodeHookFunc(
// mapstructure.StringToTimeDurationHookFunc(),
// mapstructure.StringToSliceHookFunc(","),
// )
func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
return func(c *mapstructure.DecoderConfig) {
c.DecodeHook = hook
@@ -156,18 +156,18 @@ func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption {
//
// For example, if values from the following sources were loaded:
//
// Defaults : {
// "secret": "",
// "user": "default",
// "endpoint": "https://localhost"
// }
// Config : {
// "user": "root"
// "secret": "defaultsecret"
// }
// Env : {
// "secret": "somesecretkey"
// }
// Defaults : {
// "secret": "",
// "user": "default",
// "endpoint": "https://localhost"
// }
// Config : {
// "user": "root"
// "secret": "defaultsecret"
// }
// Env : {
// "secret": "somesecretkey"
// }
//
// The resulting config will have the following values:
//
@@ -215,11 +215,13 @@ type Viper struct {
aliases map[string]string
typeByDefValue bool
// Store read properties on the object so that we can write back in order with comments.
// This will only be used if the configuration read is a properties file.
properties *properties.Properties
onConfigChange func(fsnotify.Event)
logger Logger
// TODO: should probably be protected with a mutex
encoderRegistry *encoding.EncoderRegistry
decoderRegistry *encoding.DecoderRegistry
}
// New returns an initialized Viper instance.
@@ -227,7 +229,7 @@ func New() *Viper {
v := new(Viper)
v.keyDelim = "."
v.configName = "config"
v.configPermissions = os.FileMode(0644)
v.configPermissions = os.FileMode(0o644)
v.fs = afero.NewOsFs()
v.config = make(map[string]interface{})
v.override = make(map[string]interface{})
@@ -237,6 +239,9 @@ func New() *Viper {
v.env = make(map[string][]string)
v.aliases = make(map[string]string)
v.typeByDefValue = false
v.logger = jwwLogger{}
v.resetEncoding()
return v
}
@@ -284,6 +289,8 @@ func NewWithOptions(opts ...Option) *Viper {
opt.apply(v)
}
v.resetEncoding()
return v
}
@@ -292,8 +299,86 @@ func NewWithOptions(opts ...Option) *Viper {
// can use it in their testing as well.
func Reset() {
v = New()
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
SupportedRemoteProviders = []string{"etcd", "consul", "firestore"}
SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
}
// TODO: make this lazy initialization instead
func (v *Viper) resetEncoding() {
encoderRegistry := encoding.NewEncoderRegistry()
decoderRegistry := encoding.NewDecoderRegistry()
{
codec := yaml.Codec{}
encoderRegistry.RegisterEncoder("yaml", codec)
decoderRegistry.RegisterDecoder("yaml", codec)
encoderRegistry.RegisterEncoder("yml", codec)
decoderRegistry.RegisterDecoder("yml", codec)
}
{
codec := json.Codec{}
encoderRegistry.RegisterEncoder("json", codec)
decoderRegistry.RegisterDecoder("json", codec)
}
{
codec := toml.Codec{}
encoderRegistry.RegisterEncoder("toml", codec)
decoderRegistry.RegisterDecoder("toml", codec)
}
{
codec := hcl.Codec{}
encoderRegistry.RegisterEncoder("hcl", codec)
decoderRegistry.RegisterDecoder("hcl", codec)
encoderRegistry.RegisterEncoder("tfvars", codec)
decoderRegistry.RegisterDecoder("tfvars", codec)
}
{
codec := ini.Codec{
KeyDelimiter: v.keyDelim,
LoadOptions: v.iniLoadOptions,
}
encoderRegistry.RegisterEncoder("ini", codec)
decoderRegistry.RegisterDecoder("ini", codec)
}
{
codec := &javaproperties.Codec{
KeyDelimiter: v.keyDelim,
}
encoderRegistry.RegisterEncoder("properties", codec)
decoderRegistry.RegisterDecoder("properties", codec)
encoderRegistry.RegisterEncoder("props", codec)
decoderRegistry.RegisterDecoder("props", codec)
encoderRegistry.RegisterEncoder("prop", codec)
decoderRegistry.RegisterDecoder("prop", codec)
}
{
codec := &dotenv.Codec{}
encoderRegistry.RegisterEncoder("dotenv", codec)
decoderRegistry.RegisterDecoder("dotenv", codec)
encoderRegistry.RegisterEncoder("env", codec)
decoderRegistry.RegisterDecoder("env", codec)
}
v.encoderRegistry = encoderRegistry
v.decoderRegistry = decoderRegistry
}
type defaultRemoteProvider struct {
@@ -331,10 +416,10 @@ type RemoteProvider interface {
}
// SupportedExts are universally supported extensions.
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"}
var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"}
// SupportedRemoteProviders are universally supported remote providers.
var SupportedRemoteProviders = []string{"etcd", "consul", "firestore"}
var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore"}
func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) }
func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) {
@@ -378,9 +463,8 @@ func (v *Viper) WatchConfig() {
// we only care about the config file with the following cases:
// 1 - if the config file was modified or created
// 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement)
const writeOrCreateMask = fsnotify.Write | fsnotify.Create
if (filepath.Clean(event.Name) == configFile &&
event.Op&writeOrCreateMask != 0) ||
(event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) ||
(currentConfigFile != "" && currentConfigFile != realConfigFile) {
realConfigFile = currentConfigFile
err := v.ReadInConfig()
@@ -390,8 +474,7 @@ func (v *Viper) WatchConfig() {
if v.onConfigChange != nil {
v.onConfigChange(event)
}
} else if filepath.Clean(event.Name) == configFile &&
event.Op&fsnotify.Remove&fsnotify.Remove != 0 {
} else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) {
eventsWG.Done()
return
}
@@ -477,8 +560,9 @@ func AddConfigPath(in string) { v.AddConfigPath(in) }
func (v *Viper) AddConfigPath(in string) {
if in != "" {
absin := absPathify(in)
jww.INFO.Println("adding", absin, "to paths to search")
absin := absPathify(v.logger, in)
v.logger.Info("adding path to search paths", "path", absin)
if !stringInSlice(absin, v.configPaths) {
v.configPaths = append(v.configPaths, absin)
}
@@ -487,7 +571,7 @@ func (v *Viper) AddConfigPath(in string) {
// AddRemoteProvider adds a remote configuration source.
// Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "consul" or "firestore" are currently supported.
// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// path is the path in the k/v store to retrieve configuration
// To retrieve a config file called myapp.json from /configs/myapp.json
@@ -502,7 +586,8 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
@@ -517,7 +602,7 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error {
// AddSecureRemoteProvider adds a remote configuration source.
// Secure Remote Providers are searched in the order they are added.
// provider is a string value: "etcd", "consul" or "firestore" are currently supported.
// provider is a string value: "etcd", "etcd3", "consul" or "firestore" are currently supported.
// endpoint is the url. etcd requires http://ip:port consul requires ip:port
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg
// path is the path in the k/v store to retrieve configuration
@@ -534,7 +619,8 @@ func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring
return UnsupportedRemoteProviderError(provider)
}
if provider != "" && endpoint != "" {
jww.INFO.Printf("adding %s:%s to remote provider list", provider, endpoint)
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint)
rp := &defaultRemoteProvider{
endpoint: endpoint,
provider: provider,
@@ -697,7 +783,8 @@ func (v *Viper) searchMapWithPathPrefixes(
// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere
// on its path in the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
// "foo.bar.baz" in a lower-priority map
//
// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{}) string {
var parentVal interface{}
for i := 1; i < len(path); i++ {
@@ -722,7 +809,8 @@ func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]interface{})
// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere
// in a sub-path of the map.
// e.g., if "foo.bar" has a value in the given map, it “shadows”
// "foo.bar.baz" in a lower-priority map
//
// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// unify input map
var m map[string]interface{}
@@ -747,7 +835,8 @@ func (v *Viper) isPathShadowedInFlatMap(path []string, mi interface{}) string {
// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere
// in the environment, when automatic env is on.
// e.g., if "foo.bar" has a value in the environment, it “shadows”
// "foo.bar.baz" in a lower-priority map
//
// "foo.bar.baz" in a lower-priority map
func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
var parentKey string
for i := 1; i < len(path); i++ {
@@ -768,11 +857,11 @@ func (v *Viper) isPathShadowedInAutoEnv(path []string) string {
// would return a string slice for the key if the key's type is inferred by
// the default value and the Get function would return:
//
// []string {"a", "b", "c"}
// []string {"a", "b", "c"}
//
// Otherwise the Get function would return:
//
// "a b c"
// "a b c"
func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) }
func (v *Viper) SetTypeByDefaultValue(enable bool) {
@@ -900,6 +989,13 @@ func (v *Viper) GetUint(key string) uint {
return cast.ToUint(v.Get(key))
}
// GetUint16 returns the value associated with the key as an unsigned integer.
func GetUint16(key string) uint16 { return v.GetUint16(key) }
func (v *Viper) GetUint16(key string) uint16 {
return cast.ToUint16(v.Get(key))
}
// GetUint32 returns the value associated with the key as an unsigned integer.
func GetUint32(key string) uint32 { return v.GetUint32(key) }
@@ -1049,9 +1145,8 @@ func (v *Viper) BindPFlags(flags *pflag.FlagSet) error {
// BindPFlag binds a specific key to a pflag (as used by cobra).
// Example (where serverCmd is a Cobra instance):
//
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
//
// serverCmd.Flags().Int("port", 1138, "Port to run Application server on")
// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port"))
func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) }
func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error {
@@ -1109,6 +1204,17 @@ func (v *Viper) BindEnv(input ...string) error {
return nil
}
// MustBindEnv wraps BindEnv in a panic.
// If there is an error binding an environment variable, MustBindEnv will
// panic.
func MustBindEnv(input ...string) { v.MustBindEnv(input...) }
func (v *Viper) MustBindEnv(input ...string) {
if err := v.BindEnv(input...); err != nil {
panic(fmt.Sprintf("error while binding environment variable: %v", err))
}
}
// Given a key, find the value.
//
// Viper will check to see if an alias exists first.
@@ -1350,14 +1456,15 @@ func (v *Viper) registerAlias(alias string, key string) {
v.aliases[alias] = key
}
} else {
jww.WARN.Println("Creating circular reference alias", alias, key, v.realKey(key))
v.logger.Warn("creating circular reference alias", "alias", alias, "key", key, "real_key", v.realKey(key))
}
}
func (v *Viper) realKey(key string) string {
newkey, exists := v.aliases[key]
if exists {
jww.DEBUG.Println("Alias", key, "to", newkey)
v.logger.Debug("key is an alias", "alias", key, "to", newkey)
return v.realKey(newkey)
}
return key
@@ -1367,11 +1474,13 @@ func (v *Viper) realKey(key string) string {
func InConfig(key string) bool { return v.InConfig(key) }
func (v *Viper) InConfig(key string) bool {
// if the requested key is an alias, then return the proper key
key = v.realKey(key)
lcaseKey := strings.ToLower(key)
_, exists := v.config[key]
return exists
// if the requested key is an alias, then return the proper key
lcaseKey = v.realKey(lcaseKey)
path := strings.Split(lcaseKey, v.keyDelim)
return v.searchIndexableWithPathPrefixes(v.config, path) != nil
}
// SetDefault sets the default value for this key.
@@ -1416,7 +1525,7 @@ func (v *Viper) Set(key string, value interface{}) {
func ReadInConfig() error { return v.ReadInConfig() }
func (v *Viper) ReadInConfig() error {
jww.INFO.Println("Attempting to read in config file")
v.logger.Info("attempting to read in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
@@ -1426,7 +1535,7 @@ func (v *Viper) ReadInConfig() error {
return UnsupportedConfigError(v.getConfigType())
}
jww.DEBUG.Println("Reading file: ", filename)
v.logger.Debug("reading file", "file", filename)
file, err := afero.ReadFile(v.fs, filename)
if err != nil {
return err
@@ -1447,7 +1556,7 @@ func (v *Viper) ReadInConfig() error {
func MergeInConfig() error { return v.MergeInConfig() }
func (v *Viper) MergeInConfig() error {
jww.INFO.Println("Attempting to merge in config file")
v.logger.Info("attempting to merge in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
@@ -1538,11 +1647,12 @@ func (v *Viper) SafeWriteConfigAs(filename string) error {
}
func (v *Viper) writeConfig(filename string, force bool) error {
jww.INFO.Println("Attempting to write configuration to file.")
v.logger.Info("attempting to write configuration to file")
var configType string
ext := filepath.Ext(filename)
if ext != "" {
if ext != "" && ext != filepath.Base(filename) {
configType = ext[1:]
} else {
configType = v.configType
@@ -1584,77 +1694,12 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
buf := new(bytes.Buffer)
buf.ReadFrom(in)
switch strings.ToLower(v.getConfigType()) {
case "yaml", "yml":
if err := yaml.Unmarshal(buf.Bytes(), &c); err != nil {
return ConfigParseError{err}
}
case "json":
if err := json.Unmarshal(buf.Bytes(), &c); err != nil {
return ConfigParseError{err}
}
case "hcl":
obj, err := hcl.Parse(buf.String())
switch format := strings.ToLower(v.getConfigType()); format {
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env":
err := v.decoderRegistry.Decode(format, buf.Bytes(), c)
if err != nil {
return ConfigParseError{err}
}
if err = hcl.DecodeObject(&c, obj); err != nil {
return ConfigParseError{err}
}
case "toml":
tree, err := toml.LoadReader(buf)
if err != nil {
return ConfigParseError{err}
}
tmap := tree.ToMap()
for k, v := range tmap {
c[k] = v
}
case "dotenv", "env":
env, err := gotenv.StrictParse(buf)
if err != nil {
return ConfigParseError{err}
}
for k, v := range env {
c[k] = v
}
case "properties", "props", "prop":
v.properties = properties.NewProperties()
var err error
if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil {
return ConfigParseError{err}
}
for _, key := range v.properties.Keys() {
value, _ := v.properties.Get(key)
// recursively build nested maps
path := strings.Split(key, ".")
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(c, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
case "ini":
cfg := ini.Empty(v.iniLoadOptions)
err := cfg.Append(buf.Bytes())
if err != nil {
return ConfigParseError{err}
}
sections := cfg.Sections()
for i := 0; i < len(sections); i++ {
section := sections[i]
keys := section.Keys()
for j := 0; j < len(keys); j++ {
key := keys[j]
value := cfg.Section(section.Name()).Key(key.Name()).String()
c[section.Name()+"."+key.Name()] = value
}
}
}
insensitiviseMap(c)
@@ -1665,92 +1710,16 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
func (v *Viper) marshalWriter(f afero.File, configType string) error {
c := v.AllSettings()
switch configType {
case "json":
b, err := json.MarshalIndent(c, "", " ")
case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env":
b, err := v.encoderRegistry.Encode(configType, c)
if err != nil {
return ConfigMarshalError{err}
}
_, err = f.WriteString(string(b))
if err != nil {
return ConfigMarshalError{err}
}
case "hcl":
b, err := json.Marshal(c)
if err != nil {
return ConfigMarshalError{err}
}
ast, err := hcl.Parse(string(b))
if err != nil {
return ConfigMarshalError{err}
}
err = printer.Fprint(f, ast.Node)
if err != nil {
return ConfigMarshalError{err}
}
case "prop", "props", "properties":
if v.properties == nil {
v.properties = properties.NewProperties()
}
p := v.properties
for _, key := range v.AllKeys() {
_, _, err := p.Set(key, v.GetString(key))
if err != nil {
return ConfigMarshalError{err}
}
}
_, err := p.WriteComment(f, "#", properties.UTF8)
if err != nil {
return ConfigMarshalError{err}
}
case "dotenv", "env":
lines := []string{}
for _, key := range v.AllKeys() {
envName := strings.ToUpper(strings.Replace(key, ".", "_", -1))
val := v.Get(key)
lines = append(lines, fmt.Sprintf("%v=%v", envName, val))
}
s := strings.Join(lines, "\n")
if _, err := f.WriteString(s); err != nil {
return ConfigMarshalError{err}
}
case "toml":
t, err := toml.TreeFromMap(c)
if err != nil {
return ConfigMarshalError{err}
}
s := t.String()
if _, err := f.WriteString(s); err != nil {
return ConfigMarshalError{err}
}
case "yaml", "yml":
b, err := yaml.Marshal(c)
if err != nil {
return ConfigMarshalError{err}
}
if _, err = f.WriteString(string(b)); err != nil {
return ConfigMarshalError{err}
}
case "ini":
keys := v.AllKeys()
cfg := ini.Empty()
ini.PrettyFormat = false
for i := 0; i < len(keys); i++ {
key := keys[i]
lastSep := strings.LastIndex(key, ".")
sectionName := key[:(lastSep)]
keyName := key[(lastSep + 1):]
if sectionName == "default" {
sectionName = ""
}
cfg.Section(sectionName).Key(keyName).SetValue(v.GetString(key))
}
cfg.WriteTo(f)
}
return nil
}
@@ -1767,7 +1736,8 @@ func keyExists(k string, m map[string]interface{}) string {
}
func castToMapStringInterface(
src map[interface{}]interface{}) map[string]interface{} {
src map[interface{}]interface{},
) map[string]interface{} {
tgt := map[string]interface{}{}
for k, v := range src {
tgt[fmt.Sprintf("%v", k)] = v
@@ -1805,11 +1775,12 @@ func castMapFlagToMapInterface(src map[string]FlagValue) map[string]interface{}
// deep. Both map types are supported as there is a go-yaml fork that uses
// `map[string]interface{}` instead.
func mergeMaps(
src, tgt map[string]interface{}, itgt map[interface{}]interface{}) {
src, tgt map[string]interface{}, itgt map[interface{}]interface{},
) {
for sk, sv := range src {
tk := keyExists(sk, tgt)
if tk == "" {
jww.TRACE.Printf("tk=\"\", tgt[%s]=%v", sk, sv)
v.logger.Trace("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv)
tgt[sk] = sv
if itgt != nil {
itgt[sk] = sv
@@ -1819,7 +1790,7 @@ func mergeMaps(
tv, ok := tgt[tk]
if !ok {
jww.TRACE.Printf("tgt[%s] != ok, tgt[%s]=%v", tk, sk, sv)
v.logger.Trace("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv)
tgt[sk] = sv
if itgt != nil {
itgt[sk] = sv
@@ -1829,28 +1800,52 @@ func mergeMaps(
svType := reflect.TypeOf(sv)
tvType := reflect.TypeOf(tv)
if tvType != nil && svType != tvType { // Allow for the target to be nil
jww.ERROR.Printf(
"svType != tvType; key=%s, st=%v, tt=%v, sv=%v, tv=%v",
sk, svType, tvType, sv, tv)
continue
}
jww.TRACE.Printf("processing key=%s, st=%v, tt=%v, sv=%v, tv=%v",
sk, svType, tvType, sv, tv)
v.logger.Trace(
"processing",
"key", sk,
"st", svType,
"tt", tvType,
"sv", sv,
"tv", tv,
)
switch ttv := tv.(type) {
case map[interface{}]interface{}:
jww.TRACE.Printf("merging maps (must convert)")
tsv := sv.(map[interface{}]interface{})
v.logger.Trace("merging maps (must convert)")
tsv, ok := sv.(map[interface{}]interface{})
if !ok {
v.logger.Error(
"Could not cast sv to map[interface{}]interface{}",
"key", sk,
"st", svType,
"tt", tvType,
"sv", sv,
"tv", tv,
)
continue
}
ssv := castToMapStringInterface(tsv)
stv := castToMapStringInterface(ttv)
mergeMaps(ssv, stv, ttv)
case map[string]interface{}:
jww.TRACE.Printf("merging maps")
mergeMaps(sv.(map[string]interface{}), ttv, nil)
v.logger.Trace("merging maps")
tsv, ok := sv.(map[string]interface{})
if !ok {
v.logger.Error(
"Could not cast sv to map[string]interface{}",
"key", sk,
"st", svType,
"tt", tvType,
"sv", sv,
"tv", tv,
)
continue
}
mergeMaps(tsv, ttv, nil)
default:
jww.TRACE.Printf("setting value")
v.logger.Trace("setting value")
tgt[tk] = sv
if itgt != nil {
itgt[tk] = sv
@@ -1882,10 +1877,14 @@ func (v *Viper) getKeyValueConfig() error {
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 {
jww.ERROR.Printf("get remote config: %s", err)
v.logger.Error(fmt.Errorf("get remote config: %w", err).Error())
continue
}
@@ -1908,6 +1907,10 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{}
// Retrieve the first found remote configuration.
func (v *Viper) watchKeyValueConfigOnChannel() error {
if len(v.remoteProviders) == 0 {
return RemoteConfigError("No Remote Providers")
}
for _, rp := range v.remoteProviders {
respc, _ := RemoteConfig.WatchChannel(rp)
// Todo: Add quit channel
@@ -1925,9 +1928,15 @@ func (v *Viper) watchKeyValueConfigOnChannel() error {
// Retrieve the first found remote configuration.
func (v *Viper) watchKeyValueConfig() error {
if len(v.remoteProviders) == 0 {
return RemoteConfigError("No Remote Providers")
}
for _, rp := range v.remoteProviders {
val, err := v.watchRemoteConfig(rp)
if err != nil {
v.logger.Error(fmt.Errorf("watch remote config: %w", err).Error())
continue
}
v.kvstore = val
@@ -1970,9 +1979,10 @@ func (v *Viper) AllKeys() []string {
// flattenAndMergeMap recursively flattens the given map into a map[string]bool
// of key paths (used as a set, easier to manipulate than a []string):
// - each path is merged into a single key string, delimited with v.keyDelim
// - if a path is shadowed by an earlier value in the initial shadow map,
// it is skipped.
// - each path is merged into a single key string, delimited with v.keyDelim
// - if a path is shadowed by an earlier value in the initial shadow map,
// it is skipped.
//
// The resulting set of paths is merged to the given shadow set at the same time.
func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interface{}, prefix string) map[string]bool {
if shadow != nil && prefix != "" && shadow[prefix] {
@@ -2121,49 +2131,19 @@ func (v *Viper) getConfigFile() (string, error) {
return v.configFile, nil
}
func (v *Viper) searchInPath(in string) (filename string) {
jww.DEBUG.Println("Searching for config in ", in)
for _, ext := range SupportedExts {
jww.DEBUG.Println("Checking for", filepath.Join(in, v.configName+"."+ext))
if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
jww.DEBUG.Println("Found: ", filepath.Join(in, v.configName+"."+ext))
return filepath.Join(in, v.configName+"."+ext)
}
}
if v.configType != "" {
if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
return filepath.Join(in, v.configName)
}
}
return ""
}
// Search all configPaths for any config file.
// Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFile() (string, error) {
jww.INFO.Println("Searching for config in ", v.configPaths)
for _, cp := range v.configPaths {
file := v.searchInPath(cp)
if file != "" {
return file, nil
}
}
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
}
// Debug prints all configuration registries for debugging
// purposes.
func Debug() { v.Debug() }
func Debug() { v.Debug() }
func DebugTo(w io.Writer) { v.DebugTo(w) }
func (v *Viper) Debug() {
fmt.Printf("Aliases:\n%#v\n", v.aliases)
fmt.Printf("Override:\n%#v\n", v.override)
fmt.Printf("PFlags:\n%#v\n", v.pflags)
fmt.Printf("Env:\n%#v\n", v.env)
fmt.Printf("Key/Value Store:\n%#v\n", v.kvstore)
fmt.Printf("Config:\n%#v\n", v.config)
fmt.Printf("Defaults:\n%#v\n", v.defaults)
func (v *Viper) Debug() { v.DebugTo(os.Stdout) }
func (v *Viper) DebugTo(w io.Writer) {
fmt.Fprintf(w, "Aliases:\n%#v\n", v.aliases)
fmt.Fprintf(w, "Override:\n%#v\n", v.override)
fmt.Fprintf(w, "PFlags:\n%#v\n", v.pflags)
fmt.Fprintf(w, "Env:\n%#v\n", v.env)
fmt.Fprintf(w, "Key/Value Store:\n%#v\n", v.kvstore)
fmt.Fprintf(w, "Config:\n%#v\n", v.config)
fmt.Fprintf(w, "Defaults:\n%#v\n", v.defaults)
}
+57
View File
@@ -0,0 +1,57 @@
//go:build !go1.16 || !finder
// +build !go1.16 !finder
package viper
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/afero"
)
// Search all configPaths for any config file.
// Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFile() (string, error) {
v.logger.Info("searching for config in paths", "paths", v.configPaths)
for _, cp := range v.configPaths {
file := v.searchInPath(cp)
if file != "" {
return file, nil
}
}
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
}
func (v *Viper) searchInPath(in string) (filename string) {
v.logger.Debug("searching for config in path", "path", in)
for _, ext := range SupportedExts {
v.logger.Debug("checking if file exists", "file", filepath.Join(in, v.configName+"."+ext))
if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
v.logger.Debug("found file", "file", filepath.Join(in, v.configName+"."+ext))
return filepath.Join(in, v.configName+"."+ext)
}
}
if v.configType != "" {
if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
return filepath.Join(in, v.configName)
}
}
return ""
}
// Check if file Exists
func exists(fs afero.Fs, path string) (bool, error) {
stat, err := fs.Stat(path)
if err == nil {
return !stat.IsDir(), nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
+32
View File
@@ -0,0 +1,32 @@
//go:build go1.16 && finder
// +build go1.16,finder
package viper
import (
"fmt"
"github.com/spf13/afero"
)
// Search all configPaths for any config file.
// Returns the first path that exists (and is a config file).
func (v *Viper) findConfigFile() (string, error) {
finder := finder{
paths: v.configPaths,
fileNames: []string{v.configName},
extensions: SupportedExts,
withoutExtension: v.configType != "",
}
file, err := finder.Find(afero.NewIOFS(v.fs))
if err != nil {
return "", err
}
if file == "" {
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
}
return file, nil
}
+323 -116
View File
@@ -9,7 +9,7 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"io/ioutil" //nolint:staticcheck
"os"
"os/exec"
"path"
@@ -33,21 +33,21 @@ import (
"github.com/spf13/viper/internal/testutil"
)
var yamlExample = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
`)
// var yamlExample = []byte(`Hacker: true
// name: steve
// hobbies:
// - skateboarding
// - snowboarding
// - go
// clothing:
// jacket: leather
// trousers: denim
// pants:
// size: large
// age: 35
// eyes : brown
// beard: true
// `)
var yamlExampleWithExtras = []byte(`Existing: true
Bogus: true
@@ -263,13 +263,13 @@ func initDirs(t *testing.T) (string, string, func()) {
require.Nil(t, err)
for _, dir := range testDirs {
err = os.Mkdir(dir, 0750)
err = os.Mkdir(dir, 0o750)
assert.Nil(t, err)
err = ioutil.WriteFile(
path.Join(dir, config+".toml"),
[]byte("key = \"value is "+dir+"\"\n"),
0640)
0o640)
assert.Nil(t, err)
}
@@ -301,65 +301,192 @@ func (s *stringValue) String() string {
return string(*s)
}
func TestBasics(t *testing.T) {
SetConfigFile("/tmp/config.yaml")
filename, err := v.getConfigFile()
assert.Equal(t, "/tmp/config.yaml", filename)
assert.NoError(t, err)
func TestGetConfigFile(t *testing.T) {
t.Run("config file set", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc/viper")
v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
filename, err := v.getConfigFile()
assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename)
assert.NoError(t, err)
})
t.Run("find file", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc/viper")
filename, err := v.getConfigFile()
assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/config.yaml"), filename)
assert.NoError(t, err)
})
t.Run("find files only", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/config"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/config/config.yaml"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc")
v.AddConfigPath("/etc/config")
filename, err := v.getConfigFile()
assert.Equal(t, testutil.AbsFilePath(t, "/etc/config/config.yaml"), filename)
assert.NoError(t, err)
})
t.Run("precedence", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/home/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/home/viper/config.zml"))
require.NoError(t, err)
err = fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.bml"))
require.NoError(t, err)
err = fs.Mkdir(testutil.AbsFilePath(t, "/var/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/var/viper/config.yaml"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/home/viper")
v.AddConfigPath("/etc/viper")
v.AddConfigPath("/var/viper")
filename, err := v.getConfigFile()
assert.Equal(t, testutil.AbsFilePath(t, "/var/viper/config.yaml"), filename)
assert.NoError(t, err)
})
t.Run("without extension", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc/viper")
v.SetConfigName(".dotfilenoext")
v.SetConfigType("yaml")
filename, err := v.getConfigFile()
assert.Equal(t, testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"), filename)
assert.NoError(t, err)
})
t.Run("without extension and config type", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
_, err = fs.Create(testutil.AbsFilePath(t, "/etc/viper/.dotfilenoext"))
require.NoError(t, err)
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc/viper")
v.SetConfigName(".dotfilenoext")
_, err = v.getConfigFile()
// unless config type is set, files without extension
// are not considered
assert.Error(t, err)
})
}
func TestSearchInPath_WithoutConfigTypeSet(t *testing.T) {
filename := ".dotfilenoext"
path := "/tmp"
file := filepath.Join(path, filename)
SetConfigName(filename)
AddConfigPath(path)
_, createErr := v.fs.Create(file)
defer func() {
_ = v.fs.Remove(file)
}()
assert.NoError(t, createErr)
_, err := v.getConfigFile()
// unless config type is set, files without extension
// are not considered
assert.Error(t, err)
}
func TestReadInConfig(t *testing.T) {
t.Run("config file set", func(t *testing.T) {
fs := afero.NewMemMapFs()
func TestSearchInPath(t *testing.T) {
filename := ".dotfilenoext"
path := "/tmp"
file := filepath.Join(path, filename)
SetConfigName(filename)
SetConfigType("yaml")
AddConfigPath(path)
_, createErr := v.fs.Create(file)
defer func() {
_ = v.fs.Remove(file)
}()
assert.NoError(t, createErr)
filename, err := v.getConfigFile()
assert.Equal(t, file, filename)
assert.NoError(t, err)
}
err := fs.Mkdir("/etc/viper", 0o777)
require.NoError(t, err)
func TestSearchInPath_FilesOnly(t *testing.T) {
fs := afero.NewMemMapFs()
file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
require.NoError(t, err)
err := fs.Mkdir("/tmp/config", 0777)
require.NoError(t, err)
_, err = file.Write([]byte(`key: value`))
require.NoError(t, err)
_, err = fs.Create("/tmp/config/config.yaml")
require.NoError(t, err)
file.Close()
v := New()
v := New()
v.SetFs(fs)
v.AddConfigPath("/tmp")
v.AddConfigPath("/tmp/config")
v.SetFs(fs)
v.SetConfigFile(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
filename, err := v.getConfigFile()
assert.Equal(t, "/tmp/config/config.yaml", filename)
assert.NoError(t, err)
err = v.ReadInConfig()
require.NoError(t, err)
assert.Equal(t, "value", v.Get("key"))
})
t.Run("find file", func(t *testing.T) {
fs := afero.NewMemMapFs()
err := fs.Mkdir(testutil.AbsFilePath(t, "/etc/viper"), 0o777)
require.NoError(t, err)
file, err := fs.Create(testutil.AbsFilePath(t, "/etc/viper/config.yaml"))
require.NoError(t, err)
_, err = file.Write([]byte(`key: value`))
require.NoError(t, err)
file.Close()
v := New()
v.SetFs(fs)
v.AddConfigPath("/etc/viper")
err = v.ReadInConfig()
require.NoError(t, err)
assert.Equal(t, "value", v.Get("key"))
})
}
func TestDefault(t *testing.T) {
@@ -382,7 +509,9 @@ func TestUnmarshaling(t *testing.T) {
unmarshalReader(r, v.config)
assert.True(t, InConfig("name"))
assert.True(t, InConfig("clothing.jacket"))
assert.False(t, InConfig("state"))
assert.False(t, InConfig("clothing.hat"))
assert.Equal(t, "steve", Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, Get("hobbies"))
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, Get("clothing"))
@@ -869,7 +998,7 @@ func TestBindPFlags(t *testing.T) {
}
}
// nolint: dupl
//nolint:dupl
func TestBindPFlagsStringSlice(t *testing.T) {
tests := []struct {
Expected []string
@@ -917,7 +1046,7 @@ func TestBindPFlagsStringSlice(t *testing.T) {
}
}
// nolint: dupl
//nolint:dupl
func TestBindPFlagsStringArray(t *testing.T) {
tests := []struct {
Expected []string
@@ -965,7 +1094,7 @@ func TestBindPFlagsStringArray(t *testing.T) {
}
}
// nolint: dupl
//nolint:dupl
func TestBindPFlagsIntSlice(t *testing.T) {
tests := []struct {
Expected []int
@@ -1239,7 +1368,9 @@ func TestReadBufConfig(t *testing.T) {
t.Log(v.AllKeys())
assert.True(t, v.InConfig("name"))
assert.True(t, v.InConfig("clothing.jacket"))
assert.False(t, v.InConfig("state"))
assert.False(t, v.InConfig("clothing.hat"))
assert.Equal(t, "steve", v.Get("name"))
assert.Equal(t, []interface{}{"skateboarding", "snowboarding", "go"}, v.Get("hobbies"))
assert.Equal(t, map[string]interface{}{"jacket": "leather", "trousers": "denim", "pants": map[string]interface{}{"size": "large"}}, v.Get("clothing"))
@@ -1427,21 +1558,21 @@ p_ppu = 0.55
p_batters.batter.type = Regular
`)
var yamlWriteExpected = []byte(`age: 35
beard: true
clothing:
jacket: leather
pants:
size: large
trousers: denim
eyes: brown
hacker: true
hobbies:
- skateboarding
- snowboarding
- go
name: steve
`)
// var yamlWriteExpected = []byte(`age: 35
// beard: true
// clothing:
// jacket: leather
// pants:
// size: large
// trousers: denim
// eyes: brown
// hacker: true
// hobbies:
// - skateboarding
// - snowboarding
// - go
// name: steve
// `)
func TestWriteConfig(t *testing.T) {
fs := afero.NewMemMapFs()
@@ -1685,7 +1816,7 @@ func TestSafeWriteConfig(t *testing.T) {
v.SetConfigType("yaml")
require.NoError(t, v.ReadConfig(bytes.NewBuffer(yamlExample)))
require.NoError(t, v.SafeWriteConfig())
read, err := afero.ReadFile(fs, "/test/c.yaml")
read, err := afero.ReadFile(fs, testutil.AbsFilePath(t, "/test/c.yaml"))
require.NoError(t, err)
assert.Equal(t, yamlWriteExpected, read)
}
@@ -1702,7 +1833,7 @@ func TestSafeWriteConfigWithMissingConfigPath(t *testing.T) {
func TestSafeWriteConfigWithExistingFile(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
fs.Create("/test/c.yaml")
fs.Create(testutil.AbsFilePath(t, "/test/c.yaml"))
v.SetFs(fs)
v.AddConfigPath("/test")
v.SetConfigName("c")
@@ -1738,6 +1869,23 @@ func TestSafeWriteConfigAsWithExistingFile(t *testing.T) {
assert.True(t, ok, "Expected ConfigFileAlreadyExistsError")
}
func TestWriteHiddenFile(t *testing.T) {
v := New()
fs := afero.NewMemMapFs()
fs.Create(testutil.AbsFilePath(t, "/test/.config"))
v.SetFs(fs)
v.SetConfigName(".config")
v.SetConfigType("yaml")
v.AddConfigPath("/test")
err := v.ReadInConfig()
require.NoError(t, err)
err = v.WriteConfig()
require.NoError(t, err)
}
var yamlMergeExampleTgt = []byte(`
hello:
pop: 37890
@@ -1764,6 +1912,24 @@ hello:
fu: bar
`)
var jsonMergeExampleTgt = []byte(`
{
"hello": {
"foo": null,
"pop": 123456
}
}
`)
var jsonMergeExampleSrc = []byte(`
{
"hello": {
"foo": "foo str",
"pop": "pop str"
}
}
`)
func TestMergeConfig(t *testing.T) {
v := New()
v.SetConfigType("yml")
@@ -1787,6 +1953,10 @@ func TestMergeConfig(t *testing.T) {
t.Fatalf("uint pop != 37890, = %d", pop)
}
if pop := v.GetUint16("hello.pop"); pop != uint16(37890) {
t.Fatalf("uint pop != 37890, = %d", pop)
}
if pop := v.GetUint32("hello.pop"); pop != 37890 {
t.Fatalf("uint32 pop != 37890, = %d", pop)
}
@@ -1836,6 +2006,26 @@ func TestMergeConfig(t *testing.T) {
}
}
func TestMergeConfigOverrideType(t *testing.T) {
v := New()
v.SetConfigType("json")
if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil {
t.Fatal(err)
}
if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil {
t.Fatal(err)
}
if pop := v.GetString("hello.pop"); pop != "pop str" {
t.Fatalf("pop != \"pop str\", = %s", pop)
}
if foo := v.GetString("hello.foo"); foo != "foo str" {
t.Fatalf("foo != \"foo str\", = %s", foo)
}
}
func TestMergeConfigNoMerge(t *testing.T) {
v := New()
v.SetConfigType("yml")
@@ -2142,7 +2332,7 @@ func newViperWithConfigFile(t *testing.T) (*Viper, string, func()) {
watchDir, err := ioutil.TempDir("", "")
require.Nil(t, err)
configFile := path.Join(watchDir, "config.yaml")
err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0640)
err = ioutil.WriteFile(configFile, []byte("foo: bar\n"), 0o640)
require.Nil(t, err)
cleanup := func() {
os.RemoveAll(watchDir)
@@ -2159,11 +2349,11 @@ func newViperWithSymlinkedConfigFile(t *testing.T) (*Viper, string, string, func
watchDir, err := ioutil.TempDir("", "")
require.Nil(t, err)
dataDir1 := path.Join(watchDir, "data1")
err = os.Mkdir(dataDir1, 0777)
err = os.Mkdir(dataDir1, 0o777)
require.Nil(t, err)
realConfigFile := path.Join(dataDir1, "config.yaml")
t.Logf("Real config file location: %s\n", realConfigFile)
err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0640)
err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640)
require.Nil(t, err)
cleanup := func() {
os.RemoveAll(watchDir)
@@ -2198,13 +2388,16 @@ func TestWatchFile(t *testing.T) {
t.Logf("test config file: %s\n", configFile)
wg := sync.WaitGroup{}
wg.Add(1)
var wgDoneOnce sync.Once // OnConfigChange is called twice on Windows
v.OnConfigChange(func(in fsnotify.Event) {
t.Logf("config file changed")
wg.Done()
wgDoneOnce.Do(func() {
wg.Done()
})
})
v.WatchConfig()
// when overwriting the file and waiting for the custom change notification handler to be triggered
err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0640)
err = ioutil.WriteFile(configFile, []byte("foo: baz\n"), 0o640)
wg.Wait()
// then the config value should have changed
require.Nil(t, err)
@@ -2227,10 +2420,10 @@ func TestWatchFile(t *testing.T) {
wg.Add(1)
// when link to another `config.yaml` file
dataDir2 := path.Join(watchDir, "data2")
err := os.Mkdir(dataDir2, 0777)
err := os.Mkdir(dataDir2, 0o777)
require.Nil(t, err)
configFile2 := path.Join(dataDir2, "config.yaml")
err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0640)
err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640)
require.Nil(t, err)
// change the symlink using the `ln -sfn` command
err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()
@@ -2259,25 +2452,25 @@ func TestUnmarshal_DotSeparatorBackwardCompatibility(t *testing.T) {
assert.Equal(t, "cobra_flag", config.Foo.Bar)
}
var yamlExampleWithDot = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
emails:
steve@hacker.com:
created: 01/02/03
active: true
`)
// var yamlExampleWithDot = []byte(`Hacker: true
// name: steve
// hobbies:
// - skateboarding
// - snowboarding
// - go
// clothing:
// jacket: leather
// trousers: denim
// pants:
// size: large
// age: 35
// eyes : brown
// beard: true
// emails:
// steve@hacker.com:
// created: 01/02/03
// active: true
// `)
func TestKeyDelimiter(t *testing.T) {
v := NewWithOptions(KeyDelimiter("::"))
@@ -2327,7 +2520,10 @@ func TestKeyDelimiter(t *testing.T) {
}
var yamlDeepNestedSlices = []byte(`TV:
- title: "The expanse"
- title: "The Expanse"
title_i18n:
USA: "The Expanse"
Japan: "エクスパンス -巨獣めざめる-"
seasons:
- first_released: "December 14, 2015"
episodes:
@@ -2357,11 +2553,15 @@ func TestSliceIndexAccess(t *testing.T) {
err := v.unmarshalReader(r, v.config)
require.NoError(t, err)
assert.Equal(t, "The expanse", v.GetString("tv.0.title"))
assert.Equal(t, "The Expanse", v.GetString("tv.0.title"))
assert.Equal(t, "February 1, 2017", v.GetString("tv.0.seasons.1.first_released"))
assert.Equal(t, "Static", v.GetString("tv.0.seasons.1.episodes.2.title"))
assert.Equal(t, "December 15, 2015", v.GetString("tv.0.seasons.0.episodes.1.air_date"))
// Test nested keys with capital letters
assert.Equal(t, "The Expanse", v.GetString("tv.0.title_i18n.USA"))
assert.Equal(t, "エクスパンス -巨獣めざめる-", v.GetString("tv.0.title_i18n.Japan"))
// Test for index out of bounds
assert.Equal(t, "", v.GetString("tv.0.seasons.2.first_released"))
@@ -2405,3 +2605,10 @@ func BenchmarkGetBoolFromMap(b *testing.B) {
}
}
}
// Skip some tests on Windows that kept failing when Windows was added to the CI as a target.
func skipWindows(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skip test on Windows")
}
}
+56
View File
@@ -0,0 +1,56 @@
//go:build viper_yaml2
// +build viper_yaml2
package viper
var yamlExample = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
`)
var yamlWriteExpected = []byte(`age: 35
beard: true
clothing:
jacket: leather
pants:
size: large
trousers: denim
eyes: brown
hacker: true
hobbies:
- skateboarding
- snowboarding
- go
name: steve
`)
var yamlExampleWithDot = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
emails:
steve@hacker.com:
created: 01/02/03
active: true
`)
+56
View File
@@ -0,0 +1,56 @@
//go:build !viper_yaml2
// +build !viper_yaml2
package viper
var yamlExample = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
`)
var yamlWriteExpected = []byte(`age: 35
beard: true
clothing:
jacket: leather
pants:
size: large
trousers: denim
eyes: brown
hacker: true
hobbies:
- skateboarding
- snowboarding
- go
name: steve
`)
var yamlExampleWithDot = []byte(`Hacker: true
name: steve
hobbies:
- skateboarding
- snowboarding
- go
clothing:
jacket: leather
trousers: denim
pants:
size: large
age: 35
eyes : brown
beard: true
emails:
steve@hacker.com:
created: 01/02/03
active: true
`)
+2 -1
View File
@@ -1,4 +1,5 @@
// +build !js
//go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows
// +build darwin dragonfly freebsd openbsd linux netbsd solaris windows
package viper
+8 -6
View File
@@ -1,13 +1,19 @@
// +build js,wasm
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
package viper
import (
"errors"
"fmt"
"runtime"
"github.com/fsnotify/fsnotify"
)
func newWatcher() (*watcher, error) {
return &watcher{}, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
}
type watcher struct {
Events chan fsnotify.Event
Errors chan error
@@ -24,7 +30,3 @@ func (*watcher) Add(name string) error {
func (*watcher) Remove(name string) error {
return nil
}
func newWatcher() (*watcher, error) {
return &watcher{}, errors.New("fsnotify is not supported on WASM")
}