Skip to content

Commit 4ac8814

Browse files
committed
[config] migrate to koanf
1 parent 0890f28 commit 4ac8814

File tree

4 files changed

+144
-14
lines changed

4 files changed

+144
-14
lines changed

config/config.go

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,78 @@ import (
44
"errors"
55
"fmt"
66
"os"
7+
"strings"
78

8-
"github.com/joho/godotenv"
9-
"github.com/kelseyhightower/envconfig"
9+
"github.com/knadh/koanf/parsers/dotenv"
10+
"github.com/knadh/koanf/parsers/yaml"
11+
"github.com/knadh/koanf/providers/env"
12+
"github.com/knadh/koanf/providers/file"
13+
"github.com/knadh/koanf/providers/s3"
14+
"github.com/knadh/koanf/v2"
1015
)
1116

12-
func Load[T any](c *T) error {
13-
err := godotenv.Load()
17+
// Load reads configuration from various sources and unmarshals it into a given struct.
18+
//
19+
// It looks for configuration in the following order:
20+
// 1. S3, if `withS3` is provided.
21+
// 2. Local file, if `withYaml` is provided.
22+
// 3. `.env` file in the current working directory.
23+
// 4. Environment variables.
24+
//
25+
// If any of the above sources result in an error (other than `os.ErrNotExist`), it will be returned.
26+
//
27+
// If a source results in `os.ErrNotExist`, it will be skipped.
28+
//
29+
// The final configuration will be unmarshaled into the given struct. If unmarshaling fails, an error will be returned.
30+
func Load[T any](c *T, opts ...Option) error {
31+
options := &options{}
32+
options.apply(opts...)
33+
34+
k := koanf.New(".")
35+
36+
if options.withS3Bucket != "" && options.withS3ObjectKey != "" {
37+
accessKey := os.Getenv("AWS_ACCESS_KEY_ID")
38+
secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
39+
region := os.Getenv("AWS_REGION")
40+
endpoint := os.Getenv("AWS_ENDPOINT")
41+
42+
if accessKey == "" || secretKey == "" || region == "" {
43+
return errors.New("AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION must be set")
44+
}
45+
46+
s3Config := s3.Config{
47+
AccessKey: accessKey,
48+
SecretKey: secretKey,
49+
Region: region,
50+
Bucket: options.withS3Bucket,
51+
ObjectKey: options.withS3ObjectKey,
52+
Endpoint: endpoint,
53+
}
54+
55+
err := k.Load(s3.Provider(s3Config), yaml.Parser())
56+
if err != nil && !errors.Is(err, os.ErrNotExist) {
57+
return fmt.Errorf("load s3: %w", err)
58+
}
59+
}
60+
61+
if options.withYaml != "" {
62+
err := k.Load(file.Provider(options.withYaml), yaml.Parser())
63+
if err != nil && !errors.Is(err, os.ErrNotExist) {
64+
return fmt.Errorf("load yaml: %w", err)
65+
}
66+
}
67+
68+
err := k.Load(file.Provider(".env"), dotenv.ParserEnv("", "__", strings.ToLower))
1469
if err != nil && !errors.Is(err, os.ErrNotExist) {
15-
return fmt.Errorf("failed to load .env file: %w", err)
70+
return fmt.Errorf("load dotenv: %w", err)
1671
}
1772

18-
return loadFromEnv(c)
19-
}
73+
if err := k.Load(env.Provider("", "__", strings.ToLower), nil); err != nil {
74+
return fmt.Errorf("load env: %w", err)
75+
}
2076

21-
func loadFromEnv[T any](c *T) error {
22-
if err := envconfig.Process("", c); err != nil {
23-
return fmt.Errorf("failed to load envconfig: %w", err)
77+
if err := k.Unmarshal("", c); err != nil {
78+
return fmt.Errorf("unmarshal: %w", err)
2479
}
2580

2681
return nil

config/options.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package config
2+
3+
type options struct {
4+
withYaml string
5+
6+
withS3Bucket string
7+
withS3ObjectKey string
8+
}
9+
10+
type Option func(*options)
11+
12+
func (o *options) apply(opts ...Option) {
13+
for _, opt := range opts {
14+
opt(o)
15+
}
16+
}
17+
18+
// WithLocalYaml specifies a path to a local YAML file to load config from.
19+
// If the file does not exist, an error is not returned.
20+
func WithLocalYaml(path string) Option {
21+
return func(o *options) {
22+
o.withYaml = path
23+
}
24+
}
25+
26+
// WithS3 specifies a path to a local YAML file to load config from.
27+
// If the file does not exist, an error is not returned.
28+
func WithS3(bucket string, objectKey string) Option {
29+
return func(o *options) {
30+
o.withS3Bucket = bucket
31+
o.withS3ObjectKey = objectKey
32+
}
33+
}

go.mod

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ require (
66
github.com/go-playground/validator/v10 v10.26.0
77
github.com/gofiber/contrib/fiberzap/v2 v2.1.6
88
github.com/gofiber/fiber/v2 v2.52.9
9-
github.com/joho/godotenv v1.5.1
10-
github.com/kelseyhightower/envconfig v1.4.0
9+
github.com/knadh/koanf/parsers/dotenv v1.1.0
10+
github.com/knadh/koanf/parsers/yaml v1.1.0
11+
github.com/knadh/koanf/providers/env v1.1.0
12+
github.com/knadh/koanf/providers/file v1.2.0
13+
github.com/knadh/koanf/providers/s3 v1.0.0
14+
github.com/knadh/koanf/v2 v2.2.2
1115
github.com/redis/go-redis/v9 v9.9.0
1216
go.uber.org/fx v1.24.0
1317
go.uber.org/zap v1.27.0
@@ -17,21 +21,29 @@ require (
1721
github.com/andybalholm/brotli v1.1.0 // indirect
1822
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1923
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
24+
github.com/fsnotify/fsnotify v1.9.0 // indirect
2025
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
2126
github.com/go-playground/locales v0.14.1 // indirect
2227
github.com/go-playground/universal-translator v0.18.1 // indirect
28+
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
2329
github.com/google/uuid v1.6.0 // indirect
30+
github.com/joho/godotenv v1.5.1 // indirect
2431
github.com/klauspost/compress v1.17.9 // indirect
32+
github.com/knadh/koanf/maps v0.1.2 // indirect
2533
github.com/leodido/go-urn v1.4.0 // indirect
2634
github.com/mattn/go-colorable v0.1.13 // indirect
2735
github.com/mattn/go-isatty v0.0.20 // indirect
2836
github.com/mattn/go-runewidth v0.0.16 // indirect
37+
github.com/mitchellh/copystructure v1.2.0 // indirect
38+
github.com/mitchellh/reflectwalk v1.0.2 // indirect
39+
github.com/rhnvrm/simples3 v0.8.4 // indirect
2940
github.com/rivo/uniseg v0.2.0 // indirect
3041
github.com/valyala/bytebufferpool v1.0.0 // indirect
3142
github.com/valyala/fasthttp v1.56.0 // indirect
3243
github.com/valyala/tcplisten v1.0.0 // indirect
3344
go.uber.org/dig v1.19.0 // indirect
3445
go.uber.org/multierr v1.10.0 // indirect
46+
go.yaml.in/yaml/v3 v3.0.3 // indirect
3547
golang.org/x/crypto v0.36.0 // indirect
3648
golang.org/x/net v0.38.0 // indirect
3749
golang.org/x/sys v0.32.0 // indirect

go.sum

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1010
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1111
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1212
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
13+
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
14+
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
1315
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
1416
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
1517
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -20,6 +22,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
2022
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
2123
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
2224
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
25+
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
26+
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
2327
github.com/gofiber/contrib/fiberzap/v2 v2.1.6 h1:8aMBaO7jAB4w9o2uGC1S3ieKPxg8vfJ7t1aipq2pudg=
2428
github.com/gofiber/contrib/fiberzap/v2 v2.1.6/go.mod h1:sGrPV2XzRrI6aJQOmORr5rdk4vXLR630Oc/REtMmCYs=
2529
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
@@ -28,10 +32,26 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2832
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2933
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
3034
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
31-
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
32-
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
3335
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
3436
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
37+
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
38+
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
39+
github.com/knadh/koanf/parsers/dotenv v1.1.0 h1:dQaM0Jw54zRsqDcaJ27pciNExuKfOXagCJW3K1h0hj0=
40+
github.com/knadh/koanf/parsers/dotenv v1.1.0/go.mod h1:P3BQjxaIc2+SZ3n9BUceqYl95pz3qaGqYTZX0j0d/DI=
41+
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
42+
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
43+
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
44+
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
45+
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
46+
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
47+
github.com/knadh/koanf/providers/s3 v1.0.0 h1:VYzH2B+/pxO2WGGPV0l7ANfYSl0Q6lLkseEiGjGTNz8=
48+
github.com/knadh/koanf/providers/s3 v1.0.0/go.mod h1:urbIAj/7DiHVkBPqDh8zx5wTHSFtvtA2GjrdzAdNx/g=
49+
github.com/knadh/koanf/v2 v2.2.2 h1:ghbduIkpFui3L587wavneC9e3WIliCgiCgdxYO/wd7A=
50+
github.com/knadh/koanf/v2 v2.2.2/go.mod h1:abWQc0cBXLSF/PSOMCB/SK+T13NXDsPvOksbpi5e/9Q=
51+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
52+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
53+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
54+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
3555
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
3656
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
3757
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -41,10 +61,16 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
4161
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
4262
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
4363
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
64+
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
65+
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
66+
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
67+
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
4468
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4569
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4670
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
4771
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
72+
github.com/rhnvrm/simples3 v0.8.4 h1:w3lhMtL7Cqpi5T61gW03pPFCTHHMwtHCwczUowmLCvc=
73+
github.com/rhnvrm/simples3 v0.8.4/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
4874
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
4975
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
5076
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
@@ -65,6 +91,8 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
6591
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
6692
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
6793
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
94+
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
95+
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
6896
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
6997
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
7098
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
@@ -75,5 +103,7 @@ golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
75103
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
76104
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
77105
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
106+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
107+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
78108
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
79109
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)