diff --git a/cmd/netobserv-ebpf-agent.go b/cmd/netobserv-ebpf-agent.go index fe76df41958a0aa3a77f681f245114db3656be17..7da2c823facf704ff5bd7040e4450bcdfb5daacc 100644 --- a/cmd/netobserv-ebpf-agent.go +++ b/cmd/netobserv-ebpf-agent.go @@ -10,7 +10,7 @@ import ( "os/signal" "syscall" - "github.com/caarlos0/env/v6" + "github.com/caarlos0/env/v11" "github.com/sirupsen/logrus" "github.com/netobserv/netobserv-ebpf-agent/pkg/agent" diff --git a/go.mod b/go.mod index 6052ad4ba7f343011c12a100caf3c28bd5a471bf..106bd2b4e8f514086c01b304ca6c04849ea0c394 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 toolchain go1.23.5 require ( - github.com/caarlos0/env/v6 v6.10.1 + github.com/caarlos0/env/v11 v11.3.1 github.com/cilium/ebpf v0.17.3 github.com/fsnotify/fsnotify v1.8.0 github.com/gavv/monotime v0.0.0-20190418164738-30dba4353424 diff --git a/go.sum b/go.sum index a5a1852db4cd5ff251e6eee56f757725104b0396..4e9f72d627b4065346ac0494a05681b0ad07e93c 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= -github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenk/hub v1.0.1 h1:RBwXNOF4a8KjD8BJ08XqN8KbrqaGiQLDrgvUGJSHuPA= github.com/cenk/hub v1.0.1/go.mod h1:rJM1LNAW0ppT8FMMuPK6c2NP/R2nH/UthtuRySSaf6Y= diff --git a/vendor/github.com/caarlos0/env/v11/.editorconfig b/vendor/github.com/caarlos0/env/v11/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..0d3bd399bcdd67a92637f05f00982dd46c21e602 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/.editorconfig @@ -0,0 +1,28 @@ +[*] +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +max_line_length = 120 + +[{go.mod,go.sum,*.go}] +insert_final_newline = true +indent_size = tab +indent_style = tab +tab_width = 4 + +[Makefile] +max_line_length = off +insert_final_newline = true +indent_size = tab +indent_style = tab +tab_width = 4 + +[*.md] +max_line_length = off +trim_trailing_whitespace = false +indent_size = tab +indent_style = space +tab_width = 2 + +[.mailmap] +max_line_length = off diff --git a/vendor/github.com/caarlos0/env/v6/.gitignore b/vendor/github.com/caarlos0/env/v11/.gitignore similarity index 77% rename from vendor/github.com/caarlos0/env/v6/.gitignore rename to vendor/github.com/caarlos0/env/v11/.gitignore index ca6a0ff8cf9f77426cd8f1d03ab55e45fc665ad0..9eea1c980a29eafe8de09dff5633198d2021876d 100644 --- a/vendor/github.com/caarlos0/env/v6/.gitignore +++ b/vendor/github.com/caarlos0/env/v11/.gitignore @@ -2,3 +2,4 @@ coverage.txt bin card.png dist +codecov* diff --git a/vendor/github.com/caarlos0/env/v11/.golangci.yml b/vendor/github.com/caarlos0/env/v11/.golangci.yml new file mode 100644 index 0000000000000000000000000000000000000000..8e51bdccdae50b402d090d3fee9b54d1e9e5bb4d --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/.golangci.yml @@ -0,0 +1,32 @@ +linters-settings: + gocritic: + enabled-checks: + - emptyStringTest + - evalOrder + - paramTypeCombine + - preferStringWriter + - sprintfQuotedString + - stringConcatSimplify + - yodaStyleExpr + revive: + rules: + - name: line-length-limit + arguments: [120] + +issues: + exclude-rules: + - path: _test\.go + linters: + - revive + text: "line-length-limit:" + +linters: + enable: + - thelper + - gofumpt + - gocritic + - tparallel + - unconvert + - unparam + - wastedassign + - revive diff --git a/vendor/github.com/caarlos0/env/v6/.goreleaser.yml b/vendor/github.com/caarlos0/env/v11/.goreleaser.yml similarity index 54% rename from vendor/github.com/caarlos0/env/v6/.goreleaser.yml rename to vendor/github.com/caarlos0/env/v11/.goreleaser.yml index 4688983c27e320b99a83a6db2167f897cecebc40..a5b7d4d2720d8bbd937de95ddd92fa632f79e202 100644 --- a/vendor/github.com/caarlos0/env/v6/.goreleaser.yml +++ b/vendor/github.com/caarlos0/env/v11/.goreleaser.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema-pro.json +version: 2 includes: - from_url: url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml diff --git a/vendor/github.com/caarlos0/env/v11/.mailmap b/vendor/github.com/caarlos0/env/v11/.mailmap new file mode 100644 index 0000000000000000000000000000000000000000..eeeee60107e92c845937861d35711936c0f5fadc --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/.mailmap @@ -0,0 +1,7 @@ +Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos A Becker <caarlos0@gmail.com> +Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos A Becker <caarlos0@users.noreply.github.com> +Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Alexandro Becker <caarlos0@gmail.com> +Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Alexandro Becker <caarlos0@users.noreply.github.com> +Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Becker <caarlos0@gmail.com> +dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> +actions-user <actions@github.com> github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> diff --git a/vendor/github.com/caarlos0/env/v6/LICENSE.md b/vendor/github.com/caarlos0/env/v11/LICENSE.md similarity index 95% rename from vendor/github.com/caarlos0/env/v6/LICENSE.md rename to vendor/github.com/caarlos0/env/v11/LICENSE.md index 3a59b6b38442ff4606068be96c9d10aaf2e6f3e8..28463401b71766f5a945c529dbb1e9391ab8bc2c 100644 --- a/vendor/github.com/caarlos0/env/v6/LICENSE.md +++ b/vendor/github.com/caarlos0/env/v11/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2022 Carlos Alexandro Becker +Copyright (c) 2015-2024 Carlos Alexandro Becker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/caarlos0/env/v6/Makefile b/vendor/github.com/caarlos0/env/v11/Makefile similarity index 100% rename from vendor/github.com/caarlos0/env/v6/Makefile rename to vendor/github.com/caarlos0/env/v11/Makefile diff --git a/vendor/github.com/caarlos0/env/v11/README.md b/vendor/github.com/caarlos0/env/v11/README.md new file mode 100644 index 0000000000000000000000000000000000000000..de6f8249d15a777ca469072419f3c4968140eb11 --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/README.md @@ -0,0 +1,156 @@ +<p align="center"> + <img alt="GoReleaser Logo" src="https://becker.software/env.png" height="140" /> + <p align="center">A simple, zero-dependencies library to parse environment variables into structs.</p> +</p> + +###### Installation + +```bash +go get github.com/caarlos0/env/v11 +``` + +###### Getting started + +```go +type config struct { + Home string `env:"HOME"` +} + +// parse +var cfg config +err := env.Parse(&cfg) + +// parse with generics +cfg, err := env.ParseAs[config]() +``` + +You can see the full documentation and list of examples at [pkg.go.dev](https://pkg.go.dev/github.com/caarlos0/env/v11). + +--- + +## Used and supported by + +<p> + <a href="https://encore.dev"> + <img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" width="120px" alt="encore icon" /> + </a> + <br/> + <br/> + <b>Encore – the platform for building Go-based cloud backends.</b> + <br/> +</p> + +## Usage + +### Caveats + +> [!CAUTION] +> +> _Unexported fields_ will be **ignored** by `env`. +> This is by design and will not change. + +### Functions + +- `Parse`: parse the current environment into a type +- `ParseAs`: parse the current environment into a type using generics +- `ParseWithOptions`: parse the current environment into a type with custom options +- `ParseAsWithOptions`: parse the current environment into a type with custom options and using generics +- `Must`: can be used to wrap `Parse.*` calls to panic on error +- `GetFieldParams`: get the `env` parsed options for a type +- `GetFieldParamsWithOptions`: get the `env` parsed options for a type with custom options + +### Supported types + +Out of the box all built-in types are supported, plus a few others that are commonly used. + +Complete list: + +- `bool` +- `float32` +- `float64` +- `int16` +- `int32` +- `int64` +- `int8` +- `int` +- `string` +- `uint16` +- `uint32` +- `uint64` +- `uint8` +- `uint` +- `time.Duration` +- `time.Location` +- `encoding.TextUnmarshaler` +- `url.URL` + +Pointers, slices and slices of pointers, and maps of those types are also supported. + +You may also add custom parsers for your types. + +### Tags + +The following tags are provided: + +- `env`: sets the environment variable name and optionally takes the tag options described below +- `envDefault`: sets the default value for the field +- `envPrefix`: can be used in a field that is a complex type to set a prefix to all environment variables used in it +- `envSeparator`: sets the character to be used to separate items in slices and maps (default: `,`) +- `envKeyValSeparator`: sets the character to be used to separate keys and their values in maps (default: `:`) + +### `env` tag options + +Here are all the options available for the `env` tag: + +- `,expand`: expands environment variables, e.g. `FOO_${BAR}` +- `,file`: instructs that the content of the variable is a path to a file that should be read +- `,init`: initialize nil pointers +- `,notEmpty`: make the field errors if the environment variable is empty +- `,required`: make the field errors if the environment variable is not set +- `,unset`: unset the environment variable after use + +### Parse Options + +There are a few options available in the functions that end with `WithOptions`: + +- `Environment`: keys and values to be used instead of `os.Environ()` +- `TagName`: specifies another tag name to use rather than the default `env` +- `PrefixTagName`: specifies another prefix tag name to use rather than the default `envPrefix` +- `DefaultValueTagName`: specifies another default tag name to use rather than the default `envDefault` +- `RequiredIfNoDef`: set all `env` fields as required if they do not declare `envDefault` +- `OnSet`: allows to hook into the `env` parsing and do something when a value is set +- `Prefix`: prefix to be used in all environment variables +- `UseFieldNameByDefault`: defines whether or not `env` should use the field name by default if the `env` key is missing +- `FuncMap`: custom parse functions for custom types + +### Documentation and examples + +Examples are live in [pkg.go.dev](https://pkg.go.dev/github.com/caarlos0/env/v11), +and also in the [example test file](./example_test.go). + +## Current state + +`env` is considered feature-complete. + +I do not intent to add any new features unless they really make sense, and are +requested by many people. + +Eventual bug fixes will keep being merged. + +## Badges + +[](https://github.com/goreleaser/goreleaser/releases/latest) +[](/LICENSE.md) +[](https://github.com/caarlos0/env/actions?workflow=build) +[](https://codecov.io/gh/caarlos0/env) +[](http://godoc.org/github.com/caarlos0/env/v11) +[](https://github.com/goreleaser) +[](https://conventionalcommits.org) + +## Related projects + +- [envdoc](https://github.com/g4s8/envdoc) - generate documentation for environment variables from `env` tags + +## Stargazers over time + +[](https://starchart.cc/caarlos0/env) diff --git a/vendor/github.com/caarlos0/env/v11/env.go b/vendor/github.com/caarlos0/env/v11/env.go new file mode 100644 index 0000000000000000000000000000000000000000..805ee2dee6da3680b04f2222f9e8d10748ee0e4e --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/env.go @@ -0,0 +1,845 @@ +// Package env is a simple, zero-dependencies library to parse environment +// variables into structs. +// +// Example: +// +// type config struct { +// Home string `env:"HOME"` +// } +// // parse +// var cfg config +// err := env.Parse(&cfg) +// // or parse with generics +// cfg, err := env.ParseAs[config]() +// +// Check the examples and README for more detailed usage. +package env + +import ( + "encoding" + "fmt" + "net/url" + "os" + "reflect" + "strconv" + "strings" + "time" + "unicode" +) + +// nolint: gochecknoglobals +var ( + defaultBuiltInParsers = map[reflect.Kind]ParserFunc{ + reflect.Bool: func(v string) (interface{}, error) { + return strconv.ParseBool(v) + }, + reflect.String: func(v string) (interface{}, error) { + return v, nil + }, + reflect.Int: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 32) + return int(i), err + }, + reflect.Int16: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 16) + return int16(i), err + }, + reflect.Int32: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 32) + return int32(i), err + }, + reflect.Int64: func(v string) (interface{}, error) { + return strconv.ParseInt(v, 10, 64) + }, + reflect.Int8: func(v string) (interface{}, error) { + i, err := strconv.ParseInt(v, 10, 8) + return int8(i), err + }, + reflect.Uint: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 32) + return uint(i), err + }, + reflect.Uint16: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 16) + return uint16(i), err + }, + reflect.Uint32: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 32) + return uint32(i), err + }, + reflect.Uint64: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 64) + return i, err + }, + reflect.Uint8: func(v string) (interface{}, error) { + i, err := strconv.ParseUint(v, 10, 8) + return uint8(i), err + }, + reflect.Float64: func(v string) (interface{}, error) { + return strconv.ParseFloat(v, 64) + }, + reflect.Float32: func(v string) (interface{}, error) { + f, err := strconv.ParseFloat(v, 32) + return float32(f), err + }, + } +) + +func defaultTypeParsers() map[reflect.Type]ParserFunc { + return map[reflect.Type]ParserFunc{ + reflect.TypeOf(url.URL{}): parseURL, + reflect.TypeOf(time.Nanosecond): parseDuration, + reflect.TypeOf(time.Location{}): parseLocation, + } +} + +func parseURL(v string) (interface{}, error) { + u, err := url.Parse(v) + if err != nil { + return nil, newParseValueError("unable to parse URL", err) + } + return *u, nil +} + +func parseDuration(v string) (interface{}, error) { + d, err := time.ParseDuration(v) + if err != nil { + return nil, newParseValueError("unable to parse duration", err) + } + return d, err +} + +func parseLocation(v string) (interface{}, error) { + loc, err := time.LoadLocation(v) + if err != nil { + return nil, newParseValueError("unable to parse location", err) + } + return *loc, nil +} + +// ParserFunc defines the signature of a function that can be used within +// `Options`' `FuncMap`. +type ParserFunc func(v string) (interface{}, error) + +// OnSetFn is a hook that can be run when a value is set. +type OnSetFn func(tag string, value interface{}, isDefault bool) + +// processFieldFn is a function which takes all information about a field and processes it. +type processFieldFn func( + refField reflect.Value, + refTypeField reflect.StructField, + opts Options, + fieldParams FieldParams, +) error + +// Options for the parser. +type Options struct { + // Environment keys and values that will be accessible for the service. + Environment map[string]string + + // TagName specifies another tag name to use rather than the default 'env'. + TagName string + + // PrefixTagName specifies another prefix tag name to use rather than the default 'envPrefix'. + PrefixTagName string + + // DefaultValueTagName specifies another default tag name to use rather than the default 'envDefault'. + DefaultValueTagName string + + // RequiredIfNoDef automatically sets all fields as required if they do not + // declare 'envDefault'. + RequiredIfNoDef bool + + // OnSet allows to run a function when a value is set. + OnSet OnSetFn + + // Prefix define a prefix for every key. + Prefix string + + // UseFieldNameByDefault defines whether or not `env` should use the field + // name by default if the `env` key is missing. + // Note that the field name will be "converted" to conform with environment + // variable names conventions. + UseFieldNameByDefault bool + + // Custom parse functions for different types. + FuncMap map[reflect.Type]ParserFunc + + // Used internally. maps the env variable key to its resolved string value. + // (for env var expansion) + rawEnvVars map[string]string +} + +func (opts *Options) getRawEnv(s string) string { + val := opts.rawEnvVars[s] + if val == "" { + val = opts.Environment[s] + } + return os.Expand(val, opts.getRawEnv) +} + +func defaultOptions() Options { + return Options{ + TagName: "env", + PrefixTagName: "envPrefix", + DefaultValueTagName: "envDefault", + Environment: toMap(os.Environ()), + FuncMap: defaultTypeParsers(), + rawEnvVars: make(map[string]string), + } +} + +func mergeOptions[T any](target, source *T) { + targetPtr := reflect.ValueOf(target).Elem() + sourcePtr := reflect.ValueOf(source).Elem() + + targetType := targetPtr.Type() + for i := 0; i < targetPtr.NumField(); i++ { + fieldName := targetType.Field(i).Name + targetField := targetPtr.Field(i) + sourceField := sourcePtr.FieldByName(fieldName) + + if targetField.CanSet() && !isZero(sourceField) { + // FuncMaps are being merged, while Environments must be overwritten + if fieldName == "FuncMap" { + if !sourceField.IsZero() { + iter := sourceField.MapRange() + for iter.Next() { + targetField.SetMapIndex(iter.Key(), iter.Value()) + } + } + } else { + targetField.Set(sourceField) + } + } + } +} + +func isZero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Func, reflect.Map, reflect.Slice: + return v.IsNil() + default: + zero := reflect.Zero(v.Type()) + return v.Interface() == zero.Interface() + } +} + +func customOptions(opts Options) Options { + defOpts := defaultOptions() + mergeOptions(&defOpts, &opts) + return defOpts +} + +func optionsWithSliceEnvPrefix(opts Options, index int) Options { + return Options{ + Environment: opts.Environment, + TagName: opts.TagName, + PrefixTagName: opts.PrefixTagName, + DefaultValueTagName: opts.DefaultValueTagName, + RequiredIfNoDef: opts.RequiredIfNoDef, + OnSet: opts.OnSet, + Prefix: fmt.Sprintf("%s%d_", opts.Prefix, index), + UseFieldNameByDefault: opts.UseFieldNameByDefault, + FuncMap: opts.FuncMap, + rawEnvVars: opts.rawEnvVars, + } +} + +func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options { + return Options{ + Environment: opts.Environment, + TagName: opts.TagName, + PrefixTagName: opts.PrefixTagName, + DefaultValueTagName: opts.DefaultValueTagName, + RequiredIfNoDef: opts.RequiredIfNoDef, + OnSet: opts.OnSet, + Prefix: opts.Prefix + field.Tag.Get(opts.PrefixTagName), + UseFieldNameByDefault: opts.UseFieldNameByDefault, + FuncMap: opts.FuncMap, + rawEnvVars: opts.rawEnvVars, + } +} + +// Parse parses a struct containing `env` tags and loads its values from +// environment variables. +func Parse(v interface{}) error { + return parseInternal(v, setField, defaultOptions()) +} + +// ParseWithOptions parses a struct containing `env` tags and loads its values from +// environment variables. +func ParseWithOptions(v interface{}, opts Options) error { + return parseInternal(v, setField, customOptions(opts)) +} + +// ParseAs parses the given struct type containing `env` tags and loads its +// values from environment variables. +func ParseAs[T any]() (T, error) { + var t T + err := Parse(&t) + return t, err +} + +// ParseWithOptions parses the given struct type containing `env` tags and +// loads its values from environment variables. +func ParseAsWithOptions[T any](opts Options) (T, error) { + var t T + err := ParseWithOptions(&t, opts) + return t, err +} + +// Must panic is if err is not nil, and returns t otherwise. +func Must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +// GetFieldParams parses a struct containing `env` tags and returns information about +// tags it found. +func GetFieldParams(v interface{}) ([]FieldParams, error) { + return GetFieldParamsWithOptions(v, defaultOptions()) +} + +// GetFieldParamsWithOptions parses a struct containing `env` tags and returns information about +// tags it found. +func GetFieldParamsWithOptions(v interface{}, opts Options) ([]FieldParams, error) { + var result []FieldParams + err := parseInternal( + v, + func(_ reflect.Value, _ reflect.StructField, _ Options, fieldParams FieldParams) error { + if fieldParams.OwnKey != "" { + result = append(result, fieldParams) + } + return nil + }, + customOptions(opts), + ) + if err != nil { + return nil, err + } + + return result, nil +} + +func parseInternal(v interface{}, processField processFieldFn, opts Options) error { + ptrRef := reflect.ValueOf(v) + if ptrRef.Kind() != reflect.Ptr { + return newAggregateError(NotStructPtrError{}) + } + ref := ptrRef.Elem() + if ref.Kind() != reflect.Struct { + return newAggregateError(NotStructPtrError{}) + } + + return doParse(ref, processField, opts) +} + +func doParse(ref reflect.Value, processField processFieldFn, opts Options) error { + refType := ref.Type() + + var agrErr AggregateError + + for i := 0; i < refType.NumField(); i++ { + refField := ref.Field(i) + refTypeField := refType.Field(i) + + if err := doParseField(refField, refTypeField, processField, opts); err != nil { + if val, ok := err.(AggregateError); ok { + agrErr.Errors = append(agrErr.Errors, val.Errors...) + } else { + agrErr.Errors = append(agrErr.Errors, err) + } + } + } + + if len(agrErr.Errors) == 0 { + return nil + } + + return agrErr +} + +func doParseField( + refField reflect.Value, + refTypeField reflect.StructField, + processField processFieldFn, + opts Options, +) error { + if !refField.CanSet() { + return nil + } + if refField.Kind() == reflect.Ptr && refField.Elem().Kind() == reflect.Struct && !refField.IsNil() { + return parseInternal(refField.Interface(), processField, optionsWithEnvPrefix(refTypeField, opts)) + } + if refField.Kind() == reflect.Struct && refField.CanAddr() && refField.Type().Name() == "" { + return parseInternal(refField.Addr().Interface(), processField, optionsWithEnvPrefix(refTypeField, opts)) + } + + params, err := parseFieldParams(refTypeField, opts) + if err != nil { + return err + } + + if params.Ignored { + return nil + } + + if err := processField(refField, refTypeField, opts, params); err != nil { + return err + } + + if params.Init && isInvalidPtr(refField) { + refField.Set(reflect.New(refField.Type().Elem())) + refField = refField.Elem() + } + + if refField.Kind() == reflect.Struct { + return doParse(refField, processField, optionsWithEnvPrefix(refTypeField, opts)) + } + + if isSliceOfStructs(refTypeField) { + return doParseSlice(refField, processField, optionsWithEnvPrefix(refTypeField, opts)) + } + + return nil +} + +func isSliceOfStructs(refTypeField reflect.StructField) bool { + field := refTypeField.Type + + // *[]struct + if field.Kind() == reflect.Ptr { + field = field.Elem() + if field.Kind() == reflect.Slice && field.Elem().Kind() == reflect.Struct { + return true + } + } + + // []struct{} + if field.Kind() == reflect.Slice && field.Elem().Kind() == reflect.Struct { + return true + } + + return false +} + +func doParseSlice(ref reflect.Value, processField processFieldFn, opts Options) error { + if opts.Prefix != "" && !strings.HasSuffix(opts.Prefix, string(underscore)) { + opts.Prefix += string(underscore) + } + + var environments []string + for environment := range opts.Environment { + if strings.HasPrefix(environment, opts.Prefix) { + environments = append(environments, environment) + } + } + + if len(environments) > 0 { + counter := 0 + for finished := false; !finished; { + finished = true + prefix := fmt.Sprintf("%s%d%c", opts.Prefix, counter, underscore) + for _, variable := range environments { + if strings.HasPrefix(variable, prefix) { + counter++ + finished = false + break + } + } + } + + sliceType := ref.Type() + var initialized int + if reflect.Ptr == ref.Kind() { + sliceType = sliceType.Elem() + // Due to the rest of code the pre-initialized slice has no chance for this situation + initialized = 0 + } else { + initialized = ref.Len() + } + + var capacity int + if capacity = initialized; counter > initialized { + capacity = counter + } + result := reflect.MakeSlice(sliceType, capacity, capacity) + for i := 0; i < capacity; i++ { + item := result.Index(i) + if i < initialized { + item.Set(ref.Index(i)) + } + if err := doParse(item, processField, optionsWithSliceEnvPrefix(opts, i)); err != nil { + return err + } + } + + if result.Len() > 0 { + if reflect.Ptr == ref.Kind() { + resultPtr := reflect.New(sliceType) + resultPtr.Elem().Set(result) + result = resultPtr + } + ref.Set(result) + } + } + + return nil +} + +func setField(refField reflect.Value, refTypeField reflect.StructField, opts Options, fieldParams FieldParams) error { + value, err := get(fieldParams, opts) + if err != nil { + return err + } + + if value != "" { + return set(refField, refTypeField, value, opts.FuncMap) + } + + return nil +} + +const underscore rune = '_' + +func toEnvName(input string) string { + var output []rune + for i, c := range input { + if c == underscore { + continue + } + if len(output) > 0 && unicode.IsUpper(c) { + if len(input) > i+1 { + peek := rune(input[i+1]) + if unicode.IsLower(peek) || unicode.IsLower(rune(input[i-1])) { + output = append(output, underscore) + } + } + } + output = append(output, unicode.ToUpper(c)) + } + return string(output) +} + +// FieldParams contains information about parsed field tags. +type FieldParams struct { + OwnKey string + Key string + DefaultValue string + HasDefaultValue bool + Required bool + LoadFile bool + Unset bool + NotEmpty bool + Expand bool + Init bool + Ignored bool +} + +func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) { + ownKey, tags := parseKeyForOption(field.Tag.Get(opts.TagName)) + if ownKey == "" && opts.UseFieldNameByDefault { + ownKey = toEnvName(field.Name) + } + + defaultValue, hasDefaultValue := field.Tag.Lookup(opts.DefaultValueTagName) + + result := FieldParams{ + OwnKey: ownKey, + Key: opts.Prefix + ownKey, + Required: opts.RequiredIfNoDef, + DefaultValue: defaultValue, + HasDefaultValue: hasDefaultValue, + Ignored: ownKey == "-", + } + + for _, tag := range tags { + switch tag { + case "": + continue + case "file": + result.LoadFile = true + case "required": + result.Required = true + case "unset": + result.Unset = true + case "notEmpty": + result.NotEmpty = true + case "expand": + result.Expand = true + case "init": + result.Init = true + case "-": + result.Ignored = true + default: + return FieldParams{}, newNoSupportedTagOptionError(tag) + } + } + + return result, nil +} + +func get(fieldParams FieldParams, opts Options) (val string, err error) { + var exists, isDefault bool + + val, exists, isDefault = getOr( + fieldParams.Key, + fieldParams.DefaultValue, + fieldParams.HasDefaultValue, + opts.Environment, + ) + + if fieldParams.Expand { + val = os.Expand(val, opts.getRawEnv) + } + + opts.rawEnvVars[fieldParams.OwnKey] = val + + if fieldParams.Unset { + defer os.Unsetenv(fieldParams.Key) + } + + if fieldParams.Required && !exists && fieldParams.OwnKey != "" { + return "", newVarIsNotSetError(fieldParams.Key) + } + + if fieldParams.NotEmpty && val == "" { + return "", newEmptyVarError(fieldParams.Key) + } + + if fieldParams.LoadFile && val != "" { + filename := val + val, err = getFromFile(filename) + if err != nil { + return "", newLoadFileContentError(filename, fieldParams.Key, err) + } + } + + if opts.OnSet != nil { + if fieldParams.OwnKey != "" { + opts.OnSet(fieldParams.Key, val, isDefault) + } + } + return val, err +} + +// split the env tag's key into the expected key and desired option, if any. +func parseKeyForOption(key string) (string, []string) { + opts := strings.Split(key, ",") + return opts[0], opts[1:] +} + +func getFromFile(filename string) (value string, err error) { + b, err := os.ReadFile(filename) + return string(b), err +} + +func getOr(key, defaultValue string, defExists bool, envs map[string]string) (val string, exists, isDefault bool) { + value, exists := envs[key] + switch { + case (!exists || key == "") && defExists: + return defaultValue, true, true + case exists && value == "" && defExists: + return defaultValue, true, true + case !exists: + return "", false, false + } + + return value, true, false +} + +func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error { + if tm := asTextUnmarshaler(field); tm != nil { + if err := tm.UnmarshalText([]byte(value)); err != nil { + return newParseError(sf, err) + } + return nil + } + + typee := sf.Type + fieldee := field + if typee.Kind() == reflect.Ptr { + typee = typee.Elem() + fieldee = field.Elem() + } + + parserFunc, ok := funcMap[typee] + if ok { + val, err := parserFunc(value) + if err != nil { + return newParseError(sf, err) + } + + fieldee.Set(reflect.ValueOf(val)) + return nil + } + + parserFunc, ok = defaultBuiltInParsers[typee.Kind()] + if ok { + val, err := parserFunc(value) + if err != nil { + return newParseError(sf, err) + } + + fieldee.Set(reflect.ValueOf(val).Convert(typee)) + return nil + } + + switch field.Kind() { + case reflect.Slice: + return handleSlice(field, value, sf, funcMap) + case reflect.Map: + return handleMap(field, value, sf, funcMap) + } + + return newNoParserError(sf) +} + +func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error { + separator := sf.Tag.Get("envSeparator") + if separator == "" { + separator = "," + } + parts := strings.Split(value, separator) + + typee := sf.Type.Elem() + if typee.Kind() == reflect.Ptr { + typee = typee.Elem() + } + + if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok { + return parseTextUnmarshalers(field, parts, sf) + } + + parserFunc, ok := funcMap[typee] + if !ok { + parserFunc, ok = defaultBuiltInParsers[typee.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + result := reflect.MakeSlice(sf.Type, 0, len(parts)) + for _, part := range parts { + r, err := parserFunc(part) + if err != nil { + return newParseError(sf, err) + } + v := reflect.ValueOf(r).Convert(typee) + if sf.Type.Elem().Kind() == reflect.Ptr { + v = reflect.New(typee) + v.Elem().Set(reflect.ValueOf(r).Convert(typee)) + } + result = reflect.Append(result, v) + } + field.Set(result) + return nil +} + +func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error { + keyType := sf.Type.Key() + keyParserFunc, ok := funcMap[keyType] + if !ok { + keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + elemType := sf.Type.Elem() + elemParserFunc, ok := funcMap[elemType] + if !ok { + elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()] + if !ok { + return newNoParserError(sf) + } + } + + separator := sf.Tag.Get("envSeparator") + if separator == "" { + separator = "," + } + + keyValSeparator := sf.Tag.Get("envKeyValSeparator") + if keyValSeparator == "" { + keyValSeparator = ":" + } + + result := reflect.MakeMap(sf.Type) + for _, part := range strings.Split(value, separator) { + pairs := strings.SplitN(part, keyValSeparator, 2) + if len(pairs) != 2 { + return newParseError(sf, fmt.Errorf(`%q should be in "key%svalue" format`, part, keyValSeparator)) + } + + key, err := keyParserFunc(pairs[0]) + if err != nil { + return newParseError(sf, err) + } + + elem, err := elemParserFunc(pairs[1]) + if err != nil { + return newParseError(sf, err) + } + + result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType)) + } + + field.Set(result) + return nil +} + +func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler { + if field.Kind() == reflect.Ptr { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + } else if field.CanAddr() { + field = field.Addr() + } + + tm, ok := field.Interface().(encoding.TextUnmarshaler) + if !ok { + return nil + } + return tm +} + +func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error { + s := len(data) + elemType := field.Type().Elem() + slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s) + for i, v := range data { + sv := slice.Index(i) + kind := sv.Kind() + if kind == reflect.Ptr { + sv = reflect.New(elemType.Elem()) + } else { + sv = sv.Addr() + } + tm := sv.Interface().(encoding.TextUnmarshaler) + if err := tm.UnmarshalText([]byte(v)); err != nil { + return newParseError(sf, err) + } + if kind == reflect.Ptr { + slice.Index(i).Set(sv) + } + } + + field.Set(slice) + + return nil +} + +// ToMap Converts list of env vars as provided by os.Environ() to map you +// can use as Options.Environment field +func ToMap(env []string) map[string]string { + return toMap(env) +} + +func isInvalidPtr(v reflect.Value) bool { + return reflect.Ptr == v.Kind() && v.Elem().Kind() == reflect.Invalid +} diff --git a/vendor/github.com/caarlos0/env/v6/env_unix.go b/vendor/github.com/caarlos0/env/v11/env_tomap.go similarity index 51% rename from vendor/github.com/caarlos0/env/v6/env_unix.go rename to vendor/github.com/caarlos0/env/v11/env_tomap.go index 411d4385a05249267f964793dfdb53581c820e57..aece2ae9af27d7ee5825b941e3239af06a25da2b 100644 --- a/vendor/github.com/caarlos0/env/v6/env_unix.go +++ b/vendor/github.com/caarlos0/env/v11/env_tomap.go @@ -1,5 +1,4 @@ -//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris -// +build darwin dragonfly freebsd linux netbsd openbsd solaris +//go:build !windows package env @@ -9,7 +8,9 @@ func toMap(env []string) map[string]string { r := map[string]string{} for _, e := range env { p := strings.SplitN(e, "=", 2) - r[p[0]] = p[1] + if len(p) == 2 { + r[p[0]] = p[1] + } } return r } diff --git a/vendor/github.com/caarlos0/env/v6/env_windows.go b/vendor/github.com/caarlos0/env/v11/env_tomap_windows.go similarity index 90% rename from vendor/github.com/caarlos0/env/v6/env_windows.go rename to vendor/github.com/caarlos0/env/v11/env_tomap_windows.go index e12123cd38fe5b3f05bcf83cb9fe67e1b02884bb..04ce66f577f4ae8fd4e0defd64f81e89947dd9af 100644 --- a/vendor/github.com/caarlos0/env/v6/env_windows.go +++ b/vendor/github.com/caarlos0/env/v11/env_tomap_windows.go @@ -1,3 +1,5 @@ +//go:build windows + package env import "strings" @@ -19,7 +21,9 @@ func toMap(env []string) map[string]string { p[0] = "=" + p[0] } - r[p[0]] = p[1] + if len(p) == 2 { + r[p[0]] = p[1] + } } return r } diff --git a/vendor/github.com/caarlos0/env/v11/error.go b/vendor/github.com/caarlos0/env/v11/error.go new file mode 100644 index 0000000000000000000000000000000000000000..d615912c4878e7b8e1352a1a797b0ae5660e5a6d --- /dev/null +++ b/vendor/github.com/caarlos0/env/v11/error.go @@ -0,0 +1,173 @@ +package env + +import ( + "fmt" + "reflect" + "strings" +) + +// An aggregated error wrapper to combine gathered errors. +// This allows either to display all errors or convert them individually +// List of the available errors +// ParseError +// NotStructPtrError +// NoParserError +// NoSupportedTagOptionError +// VarIsNotSetError +// EmptyVarError +// LoadFileContentError +// ParseValueError +type AggregateError struct { + Errors []error +} + +func newAggregateError(initErr error) error { + return AggregateError{ + []error{ + initErr, + }, + } +} + +func (e AggregateError) Error() string { + var sb strings.Builder + + sb.WriteString("env:") + + for _, err := range e.Errors { + sb.WriteString(fmt.Sprintf(" %v;", err.Error())) + } + + return strings.TrimRight(sb.String(), ";") +} + +// Unwrap implements std errors.Join go1.20 compatibility +func (e AggregateError) Unwrap() []error { + return e.Errors +} + +// Is conforms with errors.Is. +func (e AggregateError) Is(err error) bool { + for _, ie := range e.Errors { + if reflect.TypeOf(ie) == reflect.TypeOf(err) { + return true + } + } + return false +} + +// The error occurs when it's impossible to convert the value for given type. +type ParseError struct { + Name string + Type reflect.Type + Err error +} + +func newParseError(sf reflect.StructField, err error) error { + return ParseError{sf.Name, sf.Type, err} +} + +func (e ParseError) Error() string { + return fmt.Sprintf("parse error on field %q of type %q: %v", e.Name, e.Type, e.Err) +} + +// The error occurs when pass something that is not a pointer to a struct to Parse +type NotStructPtrError struct{} + +func (e NotStructPtrError) Error() string { + return "expected a pointer to a Struct" +} + +// This error occurs when there is no parser provided for given type. +type NoParserError struct { + Name string + Type reflect.Type +} + +func newNoParserError(sf reflect.StructField) error { + return NoParserError{sf.Name, sf.Type} +} + +func (e NoParserError) Error() string { + return fmt.Sprintf("no parser found for field %q of type %q", e.Name, e.Type) +} + +// This error occurs when the given tag is not supported. +// Built-in supported tags: "", "file", "required", "unset", "notEmpty", +// "expand", "envDefault", and "envSeparator". +type NoSupportedTagOptionError struct { + Tag string +} + +func newNoSupportedTagOptionError(tag string) error { + return NoSupportedTagOptionError{tag} +} + +func (e NoSupportedTagOptionError) Error() string { + return fmt.Sprintf("tag option %q not supported", e.Tag) +} + +// This error occurs when the required variable is not set. +// +// Deprecated: use VarIsNotSetError. +type EnvVarIsNotSetError = VarIsNotSetError + +// This error occurs when the required variable is not set. +type VarIsNotSetError struct { + Key string +} + +func newVarIsNotSetError(key string) error { + return VarIsNotSetError{key} +} + +func (e VarIsNotSetError) Error() string { + return fmt.Sprintf(`required environment variable %q is not set`, e.Key) +} + +// This error occurs when the variable which must be not empty is existing but has an empty value +// +// Deprecated: use EmptyVarError. +type EmptyEnvVarError = EmptyVarError + +// This error occurs when the variable which must be not empty is existing but has an empty value +type EmptyVarError struct { + Key string +} + +func newEmptyVarError(key string) error { + return EmptyVarError{key} +} + +func (e EmptyVarError) Error() string { + return fmt.Sprintf("environment variable %q should not be empty", e.Key) +} + +// This error occurs when it's impossible to load the value from the file. +type LoadFileContentError struct { + Filename string + Key string + Err error +} + +func newLoadFileContentError(filename, key string, err error) error { + return LoadFileContentError{filename, key, err} +} + +func (e LoadFileContentError) Error() string { + return fmt.Sprintf("could not load content of file %q from variable %s: %v", e.Filename, e.Key, e.Err) +} + +// This error occurs when it's impossible to convert value using given parser. +type ParseValueError struct { + Msg string + Err error +} + +func newParseValueError(message string, err error) error { + return ParseValueError{message, err} +} + +func (e ParseValueError) Error() string { + return fmt.Sprintf("%s: %v", e.Msg, e.Err) +} diff --git a/vendor/github.com/caarlos0/env/v6/.golangci.yml b/vendor/github.com/caarlos0/env/v6/.golangci.yml deleted file mode 100644 index ff791f86ea0196f9363847b82b8f5eb369a92bbd..0000000000000000000000000000000000000000 --- a/vendor/github.com/caarlos0/env/v6/.golangci.yml +++ /dev/null @@ -1,8 +0,0 @@ -linters: - enable: - - thelper - - gofumpt - - tparallel - - unconvert - - unparam - - wastedassign diff --git a/vendor/github.com/caarlos0/env/v6/README.md b/vendor/github.com/caarlos0/env/v6/README.md deleted file mode 100644 index e56399b17202eb35e81b1d0e4ed9798993d338f5..0000000000000000000000000000000000000000 --- a/vendor/github.com/caarlos0/env/v6/README.md +++ /dev/null @@ -1,452 +0,0 @@ -# env - -[](https://github.com/caarlos0/env/actions?workflow=build) -[](https://codecov.io/gh/caarlos0/env) -[](https://pkg.go.dev/github.com/caarlos0/env/v6) - -A simple and zero-dependencies library to parse environment variables into structs. - -## Example - -Get the module with: - -```sh -go get github.com/caarlos0/env/v6 -``` - -The usage looks like this: - -```go -package main - -import ( - "fmt" - "time" - - "github.com/caarlos0/env/v6" -) - -type config struct { - Home string `env:"HOME"` - Port int `env:"PORT" envDefault:"3000"` - Password string `env:"PASSWORD,unset"` - IsProduction bool `env:"PRODUCTION"` - Hosts []string `env:"HOSTS" envSeparator:":"` - Duration time.Duration `env:"DURATION"` - TempFolder string `env:"TEMP_FOLDER" envDefault:"${HOME}/tmp" envExpand:"true"` -} - -func main() { - cfg := config{} - if err := env.Parse(&cfg); err != nil { - fmt.Printf("%+v\n", err) - } - - fmt.Printf("%+v\n", cfg) -} -``` - -You can run it like this: - -```sh -$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go -{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s} -``` - -⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**. - -## Supported types and defaults - -Out of the box all built-in types are supported, plus a few others that -are commonly used. - -Complete list: - -- `string` -- `bool` -- `int` -- `int8` -- `int16` -- `int32` -- `int64` -- `uint` -- `uint8` -- `uint16` -- `uint32` -- `uint64` -- `float32` -- `float64` -- `time.Duration` -- `encoding.TextUnmarshaler` -- `url.URL` - -Pointers, slices and slices of pointers of those types are also supported. - -You can also use/define a [custom parser func](#custom-parser-funcs) for any -other type you want. - -If you set the `envDefault` tag for something, this value will be used in the -case of absence of it in the environment. - -By default, slice types will split the environment value on `,`; you can change -this behavior by setting the `envSeparator` tag. - -If you set the `envExpand` tag, environment variables (either in `${var}` or -`$var` format) in the string will be replaced according with the actual value -of the variable. - -## Custom Parser Funcs - -If you have a type that is not supported out of the box by the lib, you are able -to use (or define) and pass custom parsers (and their associated `reflect.Type`) -to the `env.ParseWithFuncs()` function. - -In addition to accepting a struct pointer (same as `Parse()`), this function -also accepts a `map[reflect.Type]env.ParserFunc`. - -If you add a custom parser for, say `Foo`, it will also be used to parse -`*Foo` and `[]Foo` types. - -Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v6) -for more info. - -### A note about `TextUnmarshaler` and `time.Time` - -Env supports by default anything that implements the `TextUnmarshaler` interface. -That includes things like `time.Time` for example. -The upside is that depending on the format you need, you don't need to change anything. -The downside is that if you do need time in another format, you'll need to create your own type. - -Its fairly straightforward: - -```go -type MyTime time.Time - -func (t *MyTime) UnmarshalText(text []byte) error { - tt, err := time.Parse("2006-01-02", string(text)) - *t = MyTime(tt) - return err -} - -type Config struct { - SomeTime MyTime `env:"SOME_TIME"` -} -``` - -And then you can parse `Config` with `env.Parse`. - -## Required fields - -The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to ensure that some environment variable is set. -In the example above, an error is returned if the `config` struct is changed to: - -```go -type config struct { - SecretKey string `env:"SECRET_KEY,required"` -} -``` - -## Not Empty fields - -While `required` demands the environment variable to be check, it doesn't check its value. -If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`). - -Example: - -```go -type config struct { - SecretKey string `env:"SECRET_KEY,notEmpty"` -} -``` - -## Unset environment variable after reading it - -The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added -to ensure that some environment variable is unset after reading it. - -Example: - -```go -type config struct { - SecretKey string `env:"SECRET_KEY,unset"` -} -``` - -## From file - -The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added -to in order to indicate that the value of the variable shall be loaded from a file. The path of that file is given -by the environment variable associated with it -Example below - -```go -package main - -import ( - "fmt" - "time" - "github.com/caarlos0/env/v6" -) - -type config struct { - Secret string `env:"SECRET,file"` - Password string `env:"PASSWORD,file" envDefault:"/tmp/password"` - Certificate string `env:"CERTIFICATE,file" envDefault:"${CERTIFICATE_FILE}" envExpand:"true"` -} - -func main() { - cfg := config{} - if err := env.Parse(&cfg); err != nil { - fmt.Printf("%+v\n", err) - } - - fmt.Printf("%+v\n", cfg) -} -``` - -```sh -$ echo qwerty > /tmp/secret -$ echo dvorak > /tmp/password -$ echo coleman > /tmp/certificate - -$ SECRET=/tmp/secret \ - CERTIFICATE_FILE=/tmp/certificate \ - go run main.go -{Secret:qwerty Password:dvorak Certificate:coleman} -``` - -## Options - -### Environment - -By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values` -as env vars before parsing is done. These envs are stored in the map and never actually set by `os.Setenv`. -This option effectively makes `env` ignore the OS environment variables: only the ones provided in the option are used. - -This can make your testing scenarios a bit more clean and easy to handle. - -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Password string `env:"PASSWORD"` -} - -func main() { - cfg := &Config{} - opts := &env.Options{Environment: map[string]string{ - "PASSWORD": "MY_PASSWORD", - }} - - // Load env vars. - if err := env.Parse(cfg, opts); err != nil { - log.Fatal(err) - } - - // Print the loaded data. - fmt.Printf("%+v\n", cfg.envData) -} -``` - -### Changing default tag name - -You can change what tag name to use for setting the env vars by setting the `Options.TagName` -variable. - -For example -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Password string `json:"PASSWORD"` -} - -func main() { - cfg := &Config{} - opts := &env.Options{TagName: "json"} - - // Load env vars. - if err := env.Parse(cfg, opts); err != nil { - log.Fatal(err) - } - - // Print the loaded data. - fmt.Printf("%+v\n", cfg.envData) -} -``` - -### Prefixes - -You can prefix sub-structs env tags, as well as a whole `env.Parse` call. - -Here's an example flexing it a bit: - -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Home string `env:"HOME"` -} - -type ComplexConfig struct { - Foo Config `envPrefix:"FOO_"` - Clean Config - Bar Config `envPrefix:"BAR_"` - Blah string `env:"BLAH"` -} - -func main() { - cfg := ComplexConfig{} - if err := Parse(&cfg, Options{ - Prefix: "T_", - Environment: map[string]string{ - "T_FOO_HOME": "/foo", - "T_BAR_HOME": "/bar", - "T_BLAH": "blahhh", - "T_HOME": "/clean", - }, - }); err != nil { - log.Fatal(err) - } - - // Load env vars. - if err := env.Parse(cfg, opts); err != nil { - log.Fatal(err) - } - - // Print the loaded data. - fmt.Printf("%+v\n", cfg.envData) -} -``` - -### On set hooks - -You might want to listen to value sets and, for example, log something or do some other kind of logic. -You can do this by passing a `OnSet` option: - -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Username string `env:"USERNAME" envDefault:"admin"` - Password string `env:"PASSWORD"` -} - -func main() { - cfg := &Config{} - opts := &env.Options{ - OnSet: func(tag string, value interface{}, isDefault bool) { - fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault) - }, - } - - // Load env vars. - if err := env.Parse(cfg, opts); err != nil { - log.Fatal(err) - } - - // Print the loaded data. - fmt.Printf("%+v\n", cfg.envData) -} -``` - -## Making all fields to required - -You can make all fields that don't have a default value be required by setting the `RequiredIfNoDef: true` in the `Options`. - -For example - -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Username string `env:"USERNAME" envDefault:"admin"` - Password string `env:"PASSWORD"` -} - -func main() { - cfg := &Config{} - opts := &env.Options{RequiredIfNoDef: true} - - // Load env vars. - if err := env.Parse(cfg, opts); err != nil { - log.Fatal(err) - } - - // Print the loaded data. - fmt.Printf("%+v\n", cfg.envData) -} -``` - -## Defaults from code - -You may define default value also in code, by initialising the config data before it's filled by `env.Parse`. -Default values defined as struct tags will overwrite existing values during Parse. - -```go -package main - -import ( - "fmt" - "log" - - "github.com/caarlos0/env/v6" -) - -type Config struct { - Username string `env:"USERNAME" envDefault:"admin"` - Password string `env:"PASSWORD"` -} - -func main() { - var cfg = Config{ - Username: "test", - Password: "123456", - } - - if err := env.Parse(&cfg); err != nil { - fmt.Println("failed:", err) - } - - fmt.Printf("%+v", cfg) // {Username:admin Password:123456} -} -``` - -## Stargazers over time - -[](https://starchart.cc/caarlos0/env) diff --git a/vendor/github.com/caarlos0/env/v6/env.go b/vendor/github.com/caarlos0/env/v6/env.go deleted file mode 100644 index f0b09df448c686b86bd6cfbcdca040f5e617f063..0000000000000000000000000000000000000000 --- a/vendor/github.com/caarlos0/env/v6/env.go +++ /dev/null @@ -1,504 +0,0 @@ -package env - -import ( - "encoding" - "errors" - "fmt" - "net/url" - "os" - "reflect" - "strconv" - "strings" - "time" -) - -// nolint: gochecknoglobals -var ( - // ErrNotAStructPtr is returned if you pass something that is not a pointer to a - // Struct to Parse. - ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct") - - defaultBuiltInParsers = map[reflect.Kind]ParserFunc{ - reflect.Bool: func(v string) (interface{}, error) { - return strconv.ParseBool(v) - }, - reflect.String: func(v string) (interface{}, error) { - return v, nil - }, - reflect.Int: func(v string) (interface{}, error) { - i, err := strconv.ParseInt(v, 10, 32) - return int(i), err - }, - reflect.Int16: func(v string) (interface{}, error) { - i, err := strconv.ParseInt(v, 10, 16) - return int16(i), err - }, - reflect.Int32: func(v string) (interface{}, error) { - i, err := strconv.ParseInt(v, 10, 32) - return int32(i), err - }, - reflect.Int64: func(v string) (interface{}, error) { - return strconv.ParseInt(v, 10, 64) - }, - reflect.Int8: func(v string) (interface{}, error) { - i, err := strconv.ParseInt(v, 10, 8) - return int8(i), err - }, - reflect.Uint: func(v string) (interface{}, error) { - i, err := strconv.ParseUint(v, 10, 32) - return uint(i), err - }, - reflect.Uint16: func(v string) (interface{}, error) { - i, err := strconv.ParseUint(v, 10, 16) - return uint16(i), err - }, - reflect.Uint32: func(v string) (interface{}, error) { - i, err := strconv.ParseUint(v, 10, 32) - return uint32(i), err - }, - reflect.Uint64: func(v string) (interface{}, error) { - i, err := strconv.ParseUint(v, 10, 64) - return i, err - }, - reflect.Uint8: func(v string) (interface{}, error) { - i, err := strconv.ParseUint(v, 10, 8) - return uint8(i), err - }, - reflect.Float64: func(v string) (interface{}, error) { - return strconv.ParseFloat(v, 64) - }, - reflect.Float32: func(v string) (interface{}, error) { - f, err := strconv.ParseFloat(v, 32) - return float32(f), err - }, - } -) - -func defaultTypeParsers() map[reflect.Type]ParserFunc { - return map[reflect.Type]ParserFunc{ - reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) { - u, err := url.Parse(v) - if err != nil { - return nil, fmt.Errorf("unable to parse URL: %v", err) - } - return *u, nil - }, - reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) { - s, err := time.ParseDuration(v) - if err != nil { - return nil, fmt.Errorf("unable to parse duration: %v", err) - } - return s, err - }, - } -} - -// ParserFunc defines the signature of a function that can be used within `CustomParsers`. -type ParserFunc func(v string) (interface{}, error) - -// OnSetFn is a hook that can be run when a value is set. -type OnSetFn func(tag string, value interface{}, isDefault bool) - -// Options for the parser. -type Options struct { - // Environment keys and values that will be accessible for the service. - Environment map[string]string - - // TagName specifies another tagname to use rather than the default env. - TagName string - - // RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault' - RequiredIfNoDef bool - - // OnSet allows to run a function when a value is set - OnSet OnSetFn - - // Prefix define a prefix for each key - Prefix string - - // Sets to true if we have already configured once. - configured bool -} - -// configure will do the basic configurations and defaults. -func configure(opts []Options) []Options { - // If we have already configured the first item - // of options will have been configured set to true. - if len(opts) > 0 && opts[0].configured { - return opts - } - - // Created options with defaults. - opt := Options{ - TagName: "env", - Environment: toMap(os.Environ()), - configured: true, - } - - // Loop over all opts structs and set - // to opt if value is not default/empty. - for _, item := range opts { - if item.Environment != nil { - opt.Environment = item.Environment - } - if item.TagName != "" { - opt.TagName = item.TagName - } - if item.OnSet != nil { - opt.OnSet = item.OnSet - } - if item.Prefix != "" { - opt.Prefix = item.Prefix - } - opt.RequiredIfNoDef = item.RequiredIfNoDef - } - - return []Options{opt} -} - -func getOnSetFn(opts []Options) OnSetFn { - return opts[0].OnSet -} - -// getTagName returns the tag name. -func getTagName(opts []Options) string { - return opts[0].TagName -} - -// getEnvironment returns the environment map. -func getEnvironment(opts []Options) map[string]string { - return opts[0].Environment -} - -// Parse parses a struct containing `env` tags and loads its values from -// environment variables. -func Parse(v interface{}, opts ...Options) error { - return ParseWithFuncs(v, map[reflect.Type]ParserFunc{}, opts...) -} - -// ParseWithFuncs is the same as `Parse` except it also allows the user to pass -// in custom parsers. -func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...Options) error { - opts = configure(opts) - - ptrRef := reflect.ValueOf(v) - if ptrRef.Kind() != reflect.Ptr { - return ErrNotAStructPtr - } - ref := ptrRef.Elem() - if ref.Kind() != reflect.Struct { - return ErrNotAStructPtr - } - parsers := defaultTypeParsers() - for k, v := range funcMap { - parsers[k] = v - } - - return doParse(ref, parsers, opts) -} - -func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error { - refType := ref.Type() - - var agrErr aggregateError - - for i := 0; i < refType.NumField(); i++ { - refField := ref.Field(i) - refTypeField := refType.Field(i) - - if err := doParseField(refField, refTypeField, funcMap, opts); err != nil { - if val, ok := err.(aggregateError); ok { - agrErr.errors = append(agrErr.errors, val.errors...) - } else { - agrErr.errors = append(agrErr.errors, err) - } - } - } - - if len(agrErr.errors) == 0 { - return nil - } - - return agrErr -} - -func doParseField(refField reflect.Value, refTypeField reflect.StructField, funcMap map[reflect.Type]ParserFunc, opts []Options) error { - if !refField.CanSet() { - return nil - } - if reflect.Ptr == refField.Kind() && refField.Elem().Kind() == reflect.Struct { - return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...) - } - if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" { - return ParseWithFuncs(refField.Addr().Interface(), funcMap, optsWithPrefix(refTypeField, opts)...) - } - value, err := get(refTypeField, opts) - if err != nil { - return err - } - - if value != "" { - return set(refField, refTypeField, value, funcMap) - } - - if reflect.Struct == refField.Kind() { - return doParse(refField, funcMap, optsWithPrefix(refTypeField, opts)) - } - - return nil -} - -func get(field reflect.StructField, opts []Options) (val string, err error) { - var exists bool - var isDefault bool - var loadFile bool - var unset bool - var notEmpty bool - - required := opts[0].RequiredIfNoDef - prefix := opts[0].Prefix - ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts))) - key := prefix + ownKey - for _, tag := range tags { - switch tag { - case "": - continue - case "file": - loadFile = true - case "required": - required = true - case "unset": - unset = true - case "notEmpty": - notEmpty = true - default: - return "", fmt.Errorf("tag option %q not supported", tag) - } - } - expand := strings.EqualFold(field.Tag.Get("envExpand"), "true") - defaultValue, defExists := field.Tag.Lookup("envDefault") - val, exists, isDefault = getOr(key, defaultValue, defExists, getEnvironment(opts)) - - if expand { - val = os.ExpandEnv(val) - } - - if unset { - defer os.Unsetenv(key) - } - - if required && !exists && len(ownKey) > 0 { - return "", fmt.Errorf(`required environment variable %q is not set`, key) - } - - if notEmpty && val == "" { - return "", fmt.Errorf("environment variable %q should not be empty", key) - } - - if loadFile && val != "" { - filename := val - val, err = getFromFile(filename) - if err != nil { - return "", fmt.Errorf(`could not load content of file "%s" from variable %s: %v`, filename, key, err) - } - } - - if onSetFn := getOnSetFn(opts); onSetFn != nil { - onSetFn(key, val, isDefault) - } - return val, err -} - -// split the env tag's key into the expected key and desired option, if any. -func parseKeyForOption(key string) (string, []string) { - opts := strings.Split(key, ",") - return opts[0], opts[1:] -} - -func getFromFile(filename string) (value string, err error) { - b, err := os.ReadFile(filename) - return string(b), err -} - -func getOr(key, defaultValue string, defExists bool, envs map[string]string) (string, bool, bool) { - value, exists := envs[key] - switch { - case (!exists || key == "") && defExists: - return defaultValue, true, true - case !exists: - return "", false, false - } - - return value, true, false -} - -func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error { - if tm := asTextUnmarshaler(field); tm != nil { - if err := tm.UnmarshalText([]byte(value)); err != nil { - return newParseError(sf, err) - } - return nil - } - - typee := sf.Type - fieldee := field - if typee.Kind() == reflect.Ptr { - typee = typee.Elem() - fieldee = field.Elem() - } - - parserFunc, ok := funcMap[typee] - if ok { - val, err := parserFunc(value) - if err != nil { - return newParseError(sf, err) - } - - fieldee.Set(reflect.ValueOf(val)) - return nil - } - - parserFunc, ok = defaultBuiltInParsers[typee.Kind()] - if ok { - val, err := parserFunc(value) - if err != nil { - return newParseError(sf, err) - } - - fieldee.Set(reflect.ValueOf(val).Convert(typee)) - return nil - } - - if field.Kind() == reflect.Slice { - return handleSlice(field, value, sf, funcMap) - } - - return newNoParserError(sf) -} - -func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error { - separator := sf.Tag.Get("envSeparator") - if separator == "" { - separator = "," - } - parts := strings.Split(value, separator) - - typee := sf.Type.Elem() - if typee.Kind() == reflect.Ptr { - typee = typee.Elem() - } - - if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok { - return parseTextUnmarshalers(field, parts, sf) - } - - parserFunc, ok := funcMap[typee] - if !ok { - parserFunc, ok = defaultBuiltInParsers[typee.Kind()] - if !ok { - return newNoParserError(sf) - } - } - - result := reflect.MakeSlice(sf.Type, 0, len(parts)) - for _, part := range parts { - r, err := parserFunc(part) - if err != nil { - return newParseError(sf, err) - } - v := reflect.ValueOf(r).Convert(typee) - if sf.Type.Elem().Kind() == reflect.Ptr { - v = reflect.New(typee) - v.Elem().Set(reflect.ValueOf(r).Convert(typee)) - } - result = reflect.Append(result, v) - } - field.Set(result) - return nil -} - -func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler { - if reflect.Ptr == field.Kind() { - if field.IsNil() { - field.Set(reflect.New(field.Type().Elem())) - } - } else if field.CanAddr() { - field = field.Addr() - } - - tm, ok := field.Interface().(encoding.TextUnmarshaler) - if !ok { - return nil - } - return tm -} - -func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error { - s := len(data) - elemType := field.Type().Elem() - slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s) - for i, v := range data { - sv := slice.Index(i) - kind := sv.Kind() - if kind == reflect.Ptr { - sv = reflect.New(elemType.Elem()) - } else { - sv = sv.Addr() - } - tm := sv.Interface().(encoding.TextUnmarshaler) - if err := tm.UnmarshalText([]byte(v)); err != nil { - return newParseError(sf, err) - } - if kind == reflect.Ptr { - slice.Index(i).Set(sv) - } - } - - field.Set(slice) - - return nil -} - -func newParseError(sf reflect.StructField, err error) error { - return parseError{ - sf: sf, - err: err, - } -} - -type parseError struct { - sf reflect.StructField - err error -} - -func (e parseError) Error() string { - return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.sf.Name, e.sf.Type, e.err) -} - -func newNoParserError(sf reflect.StructField) error { - return fmt.Errorf(`no parser found for field "%s" of type "%s"`, sf.Name, sf.Type) -} - -func optsWithPrefix(field reflect.StructField, opts []Options) []Options { - subOpts := make([]Options, len(opts)) - copy(subOpts, opts) - if prefix := field.Tag.Get("envPrefix"); prefix != "" { - subOpts[0].Prefix += prefix - } - return subOpts -} - -type aggregateError struct { - errors []error -} - -func (e aggregateError) Error() string { - var sb strings.Builder - sb.WriteString("env:") - - for _, err := range e.errors { - sb.WriteString(fmt.Sprintf(" %v;", err.Error())) - } - - return strings.TrimRight(sb.String(), ";") -} diff --git a/vendor/modules.txt b/vendor/modules.txt index a699520ce71c2a297beeaa2903e7c93035e7c823..e1b7876b3f7b87a494a6dca5b49c418ffa1ad7a7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -26,9 +26,9 @@ github.com/beorn7/perks/quantile # github.com/blang/semver/v4 v4.0.0 ## explicit; go 1.14 github.com/blang/semver/v4 -# github.com/caarlos0/env/v6 v6.10.1 -## explicit; go 1.17 -github.com/caarlos0/env/v6 +# github.com/caarlos0/env/v11 v11.3.1 +## explicit; go 1.18 +github.com/caarlos0/env/v11 # github.com/cenkalti/backoff/v4 v4.3.0 ## explicit; go 1.18 github.com/cenkalti/backoff/v4