package envconfig
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"unicode"
"go.opentelemetry.io/otel/internal/global"
)
type ConfigFn func (*EnvOptionsReader )
type EnvOptionsReader struct {
GetEnv func (string ) string
ReadFile func (string ) ([]byte , error )
Namespace string
}
func (e *EnvOptionsReader ) Apply (opts ...ConfigFn ) {
for _ , o := range opts {
o (e )
}
}
func (e *EnvOptionsReader ) GetEnvValue (key string ) (string , bool ) {
v := strings .TrimSpace (e .GetEnv (keyWithNamespace (e .Namespace , key )))
return v , v != ""
}
func WithString (n string , fn func (string )) func (e *EnvOptionsReader ) {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
fn (v )
}
}
}
func WithBool (n string , fn func (bool )) ConfigFn {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
b := strings .ToLower (v ) == "true"
fn (b )
}
}
}
func WithDuration (n string , fn func (time .Duration )) func (e *EnvOptionsReader ) {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
d , err := strconv .Atoi (v )
if err != nil {
global .Error (err , "parse duration" , "input" , v )
return
}
fn (time .Duration (d ) * time .Millisecond )
}
}
}
func WithHeaders (n string , fn func (map [string ]string )) func (e *EnvOptionsReader ) {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
fn (stringToHeader (v ))
}
}
}
func WithURL (n string , fn func (*url .URL )) func (e *EnvOptionsReader ) {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
u , err := url .Parse (v )
if err != nil {
global .Error (err , "parse url" , "input" , v )
return
}
fn (u )
}
}
}
func WithCertPool (n string , fn func (*x509 .CertPool )) ConfigFn {
return func (e *EnvOptionsReader ) {
if v , ok := e .GetEnvValue (n ); ok {
b , err := e .ReadFile (v )
if err != nil {
global .Error (err , "read tls ca cert file" , "file" , v )
return
}
c , err := createCertPool (b )
if err != nil {
global .Error (err , "create tls cert pool" )
return
}
fn (c )
}
}
}
func WithClientCert (nc , nk string , fn func (tls .Certificate )) ConfigFn {
return func (e *EnvOptionsReader ) {
vc , okc := e .GetEnvValue (nc )
vk , okk := e .GetEnvValue (nk )
if !okc || !okk {
return
}
cert , err := e .ReadFile (vc )
if err != nil {
global .Error (err , "read tls client cert" , "file" , vc )
return
}
key , err := e .ReadFile (vk )
if err != nil {
global .Error (err , "read tls client key" , "file" , vk )
return
}
crt , err := tls .X509KeyPair (cert , key )
if err != nil {
global .Error (err , "create tls client key pair" )
return
}
fn (crt )
}
}
func keyWithNamespace(ns , key string ) string {
if ns == "" {
return key
}
return fmt .Sprintf ("%s_%s" , ns , key )
}
func stringToHeader(value string ) map [string ]string {
headersPairs := strings .Split (value , "," )
headers := make (map [string ]string )
for _ , header := range headersPairs {
n , v , found := strings .Cut (header , "=" )
if !found {
global .Error (errors .New ("missing '=" ), "parse headers" , "input" , header )
continue
}
trimmedName := strings .TrimSpace (n )
if !isValidHeaderKey (trimmedName ) {
global .Error (errors .New ("invalid header key" ), "parse headers" , "key" , trimmedName )
continue
}
value , err := url .PathUnescape (v )
if err != nil {
global .Error (err , "escape header value" , "value" , v )
continue
}
trimmedValue := strings .TrimSpace (value )
headers [trimmedName ] = trimmedValue
}
return headers
}
func createCertPool(certBytes []byte ) (*x509 .CertPool , error ) {
cp := x509 .NewCertPool ()
if ok := cp .AppendCertsFromPEM (certBytes ); !ok {
return nil , errors .New ("failed to append certificate to the cert pool" )
}
return cp , nil
}
func isValidHeaderKey(key string ) bool {
if key == "" {
return false
}
for _ , c := range key {
if !isTokenChar (c ) {
return false
}
}
return true
}
func isTokenChar(c rune ) bool {
return c <= unicode .MaxASCII && (unicode .IsLetter (c ) ||
unicode .IsDigit (c ) ||
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~' )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .