diff --git a/Taskfile.yml b/Taskfile.yml index 6cd4efd..4bf5047 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -53,6 +53,12 @@ tasks: cmds: - go build -v ./... + build:generate: + desc: dummy rune + cmds: + - | + echo "Nothing to do" + format: desc: format the code cmds: diff --git a/environment/environment.go b/environment/environment.go deleted file mode 100644 index 527c53e..0000000 --- a/environment/environment.go +++ /dev/null @@ -1,103 +0,0 @@ -package environment - -import ( - "errors" - "fmt" - "os" - "strconv" - "strings" - - "github.com/datatrails/go-datatrails-common/logger" -) - -const ( - commaSeparator = "," -) - -// GetLogLevel returns the loglevet or panics. This is called before any logger -// is available. i.e. don't use a logger here. -func GetLogLevel() string { - value, ok := os.LookupEnv("LOGLEVEL") - if !ok { - panic(errors.New("No loglevel specified")) - } - return value -} - -// GetOrFatal returns the key's value or logs a Fatal error (and exits) -func GetOrFatal(key string) string { - value, ok := os.LookupEnv(key) - if !ok { - logger.Sugar.Panicf("required environment variable is not defined: %s", key) - } - return value -} - -// GetIntOrFatal returns value of environment variable that is -// expected to be an int, otherwise logs a Fatal error (and exits) -func GetIntOrFatal(key string) int { - val, ok := os.LookupEnv(key) - if !ok { - logger.Sugar.Panicf("required environment variable is not defined or: %s", key) - } - value, err := strconv.Atoi(val) - if err != nil { - logger.Sugar.Panicf("unable to convert %s value to int: %v", key, err) - } - return value -} - -// GetRequired gets the value for the key, or an error if it is not set. -func GetRequired(key string) (string, error) { - value, ok := os.LookupEnv(key) - if !ok { - return "", fmt.Errorf("required environment variable '%s' is not defined", key) - } - return value, nil -} - -// GetTruthyOrFatal returns true if key is set to a value that is truthy. Returns -// false otherwise. -func GetTruthyOrFatal(key string) bool { - value, ok := os.LookupEnv(key) - if !ok { - logger.Sugar.Panicf("environment variable %s not found", key) - } - // t,true,True,1 are all examples of 'truthy' values understood by ParseBool - b, err := strconv.ParseBool(value) - if err != nil { - logger.Sugar.Panicf("environment variable %s not valid truthy value: %v", key, err) - } - return b -} - -// GetListOrFatal returns the key's value as a list or logs a Fatal error (and exits) -// -// The value is expected to be a csv -// -// NOTE: if the value is not csv, it is returned as is in a list with the original string -// -// as the only element in the list -func GetListOrFatal(key string) []string { - const commaSeparator = "," - if value, ok := os.LookupEnv(key); ok { - values := strings.Split(value, commaSeparator) - return values - } - logger.Sugar.Panicf("required environment variable is not defined: %s", key) - return []string{} // never reaches here -} - -// ReadIndirectOrFatal reads filename and uses it to read a value from the file. -// Any error is Fatal. -func ReadIndirectOrFatal(varname string) string { - filename, ok := os.LookupEnv(varname) - if !ok { - logger.Sugar.Panicf("environment variable `%s' not present in env", varname) - } - b, err := os.ReadFile(filename) - if err != nil { - logger.Sugar.Panicf("error reading file `%s': %s", filename, err) - } - return string(b) -} diff --git a/environment/environment_test.go b/environment/environment_test.go deleted file mode 100644 index a7c1aab..0000000 --- a/environment/environment_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package environment - -import ( - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetRequiredSet(t *testing.T) { - os.Setenv("ABC", "VAL") - value, err := GetRequired("ABC") - - assert.Equal(t, "VAL", value) - assert.Nil(t, err) -} -func TestGetRequiredUnset(t *testing.T) { - os.Unsetenv("ABC") - value, err := GetRequired("ABC") - - assert.Equal(t, "", value) - assert.Equal(t, "required environment variable 'ABC' is not defined", err.Error()) -} - -// TestGetListOrFatal tests: -// -// 1. a comma separated values (csv) string is correctly separated into a list of values -// 2. a non comma separated values (csv) string is correctly returned as a list with 1 element -func TestGetListOrFatal(t *testing.T) { - type args struct { - key string - } - tests := []struct { - name string - args args - envKey string - envValue string - expected []string - }{ - { - name: "positive csv list", - args: args{ - key: "SHOPPING", - }, - envKey: "SHOPPING", - envValue: "eggs,flour,milk,sugar,candles,vanillaextract", - expected: []string{ - "eggs", - "flour", - "milk", - "sugar", - "candles", - "vanillaextract", - }, - }, - { - name: "positive not csv list", - args: args{ - key: "BOXERS", - }, - envKey: "BOXERS", - envValue: "mike tyson and rocky", - expected: []string{ - "mike tyson and rocky", - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - os.Setenv(test.envKey, test.envValue) - - // ensure we unset the env variable after every test - t.Cleanup(func() { os.Unsetenv(test.envKey) }) - - actual := GetListOrFatal(test.args.key) - - assert.Equal(t, test.expected, actual) - - }) - } -} diff --git a/go.mod b/go.mod index 888bf06..479e25c 100644 --- a/go.mod +++ b/go.mod @@ -12,12 +12,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.1 github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 - github.com/alicebob/miniredis/v2 v2.32.1 github.com/fxamacker/cbor/v2 v2.7.0 - github.com/go-redis/redis/v8 v8.11.5 github.com/google/uuid v1.6.0 - github.com/gorilla/securecookie v1.1.2 - github.com/gorilla/sessions v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/ldclabs/cose/go v0.0.0-20221214142927-d22c1cfc2154 github.com/lestrrat-go/jwx v1.2.29 @@ -36,9 +32,7 @@ require ( require ( github.com/Azure/go-amqp v1.0.5 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -51,7 +45,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/shengdoushi/base58 v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/yuin/gopher-lua v1.1.1 // indirect ) require ( @@ -64,9 +57,7 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.4 github.com/golang-jwt/jwt/v4 v4.5.1 // indirect diff --git a/go.sum b/go.sum index 9fcefc0..d24903e 100644 --- a/go.sum +++ b/go.sum @@ -40,15 +40,6 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo= -github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -57,20 +48,14 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPc github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= @@ -85,14 +70,8 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg= -github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -122,12 +101,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/nuts-foundation/go-did v0.6.4 h1:cy7BTM7MaL/bY2JbGUAy/8R9DGEM7FYg3rT5qt6oazs= github.com/nuts-foundation/go-did v0.6.4/go.mod h1:Jb3IgnO2Zeed970JMIlfjr4g1kvikmgWUJA0EfeDEFE= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= @@ -169,8 +142,6 @@ github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= -github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -200,7 +171,6 @@ golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -247,8 +217,6 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/redis/client_mock.go b/redis/client_mock.go deleted file mode 100644 index 8cc22b8..0000000 --- a/redis/client_mock.go +++ /dev/null @@ -1,41 +0,0 @@ -package redis - -// Defines Mocks for redis ClusterClient - -import ( - "time" - - "context" - - "github.com/go-redis/redis/v8" - - "github.com/stretchr/testify/mock" -) - -// mockClient is a mock redis Client -type mockClient struct { - mock.Mock -} - -func (mc *mockClient) Do(ctx context.Context, args ...any) (reply *redis.Cmd) { - - arguments := mc.Called(args...) - return arguments.Get(0).(*redis.Cmd) -} - -func (mc *mockClient) Set(ctx context.Context, key string, value any, expiration time.Duration) (reply *redis.StatusCmd) { - - arguments := mc.Called(key, value, expiration) - return arguments.Get(0).(*redis.StatusCmd) -} - -func (mc *mockClient) Close() error { - arguments := mc.Called() - return arguments.Get(0).(error) -} - -func (mc *mockClient) SetNX(ctx context.Context, key string, value any, expiration time.Duration) (reply *redis.BoolCmd) { - - arguments := mc.Called(key, value, expiration) - return arguments.Get(0).(*redis.BoolCmd) -} diff --git a/redis/cluster.go b/redis/cluster.go deleted file mode 100644 index 9664549..0000000 --- a/redis/cluster.go +++ /dev/null @@ -1,160 +0,0 @@ -package redis - -import ( - "context" - "crypto/tls" - "fmt" - "time" - - env "github.com/datatrails/go-datatrails-common/environment" - "github.com/go-redis/redis/v8" -) - -const ( - //nolint:gosec - RedisClusterPassordEnvFileSuffix = "REDIS_STORE_PASSWORD_FILENAME" - RedisClusterSizeEnvSuffix = "REDIS_CLUSTER_SIZE" - RedisNamespaceEnvSuffix = "REDIS_KEY_NAMESPACE" - RedisNodeAddressFmtSuffix = "REDIS_NODE%d_STORE_ADDRESS" - // The default implementation does 10 * GOMAXPROCS(0). GOMAXPROCS is - // problematic in containers. Note that each cluster node gets its own pool - nodePoolSize = 10 - - RedisNodeAddressSuffix = "REDIS_STORE_ADDRESS" - RedisDBSuffix = "REDIS_STORE_DB" - RedisPasswordSuffix = "AZURE_REDIS_STORE_PASSWORD_FILENAME" -) - -type RedisConfig interface { - GetClusterOptions() (*redis.ClusterOptions, error) - GetOptions() (*redis.Options, error) - Namespace() string - IsCluster() bool - URL() string - Log() Logger -} - -type Scripter interface { - Eval(ctx context.Context, script string, keys []string, args ...any) *redis.Cmd - EvalSha(ctx context.Context, sha1 string, keys []string, args ...any) *redis.Cmd - ScriptExists(ctx context.Context, hashes ...string) *redis.BoolSliceCmd - ScriptLoad(ctx context.Context, script string) *redis.StringCmd -} -type RedisClient interface { - Do(ctx context.Context, args ...any) *redis.Cmd - Del(ctx context.Context, keys ...string) *redis.IntCmd - Ping(ctx context.Context) *redis.StatusCmd - Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd - SetNX(ctx context.Context, key string, value any, expiration time.Duration) *redis.BoolCmd - Get(ctx context.Context, key string) *redis.StringCmd - Watch(ctx context.Context, fn func(*redis.Tx) error, keys ...string) error - Close() error - Scripter -} - -type clusterConfig struct { - log Logger - Size int - namespace string - clusterOptions redis.ClusterOptions - options redis.Options -} - -// ReadClusterConfigOrFatal assumes conventional service env vars and -// populates a ClusterConfig or Fatals out -func FromEnvOrFatal(log Logger) RedisConfig { - cfg := clusterConfig{log: log} - - cfg.Size = env.GetIntOrFatal(RedisClusterSizeEnvSuffix) - cfg.namespace = env.GetOrFatal(RedisNamespaceEnvSuffix) - - if cfg.Size == -1 { - cfg.options.Addr = env.GetOrFatal(RedisNodeAddressSuffix) - cfg.options.DB = env.GetIntOrFatal(RedisDBSuffix) - cfg.options.Password = env.ReadIndirectOrFatal(RedisPasswordSuffix) - return &cfg - } - - cfg.clusterOptions.Password = env.ReadIndirectOrFatal(RedisClusterPassordEnvFileSuffix) - cfg.clusterOptions.PoolSize = nodePoolSize - cfg.clusterOptions.Addrs = make([]string, 0, cfg.Size) - cfg.clusterOptions.MaxRedirects = cfg.Size - for i := range cfg.Size { - suffix := fmt.Sprintf(RedisNodeAddressFmtSuffix, i) - cfg.clusterOptions.Addrs = append( - cfg.clusterOptions.Addrs, - env.GetOrFatal(suffix), - ) - log.InfoR("Addrs", cfg.clusterOptions.Addrs) - } - - return &cfg -} - -func (cfg *clusterConfig) Log() Logger { - return cfg.log -} -func (cfg *clusterConfig) IsCluster() bool { - return cfg.Size > -1 -} - -func (cfg *clusterConfig) GetClusterOptions() (*redis.ClusterOptions, error) { - - if cfg.IsCluster() { - return &cfg.clusterOptions, nil - } - - return nil, fmt.Errorf("unexpected config type when requesting ClusterOptions") -} - -func (cfg *clusterConfig) GetOptions() (*redis.Options, error) { - - if !cfg.IsCluster() { - return &cfg.options, nil - } - - return nil, fmt.Errorf("unexpected config type when requesting Options") -} - -func (cfg *clusterConfig) Namespace() string { - return cfg.namespace -} - -func (cfg *clusterConfig) URL() string { - if cfg.IsCluster() { - if len(cfg.clusterOptions.Addrs) == 0 { - return "" - } - return cfg.clusterOptions.Addrs[0] - } - - return cfg.options.Addr -} - -func NewRedisClient(cfg RedisConfig) (RedisClient, error) { - log := cfg.Log() - - var err error - if cfg.IsCluster() { - var copts *redis.ClusterOptions - if copts, err = cfg.GetClusterOptions(); err != nil { - return nil, err - } - return redis.NewClusterClient(copts), nil - } - - var opts *redis.Options - if opts, err = cfg.GetOptions(); err != nil { - return nil, err - } - opts.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12} - log.Infof("connecting to redis: %v", opts) - c := redis.NewClient(opts) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - status := c.Ping(ctx) - if status.Err() != nil { - log.Infof("failed ping: %v (%v, %v)", status.Err(), status.FullName(), status.Args()) - } - return c, status.Err() -} diff --git a/redis/count_resource.go b/redis/count_resource.go deleted file mode 100644 index 8ac39ef..0000000 --- a/redis/count_resource.go +++ /dev/null @@ -1,344 +0,0 @@ -package redis - -import ( - "context" - "time" -) - -// ResourceCounter is a function which accepts a context and counts the number of -// reources in the database. -type ResourceCounter = func(context.Context) (int64, error) - -func defaultResourceCounter(ctx context.Context) (int64, error) { - return int64(0), nil -} - -// CountResource - the counter is initialised with a maximum value and as -// resources are created the counter is decremented. Additionally when the counter reaches -// zero the actual number of resources is counted and the current count adjusted -// accordingly. -type CountResource struct { - *Resource - - resourceCounter ResourceCounter - name string - limit int64 -} - -type CountResourceOption func(*CountResource) - -func WithResourceCounter(resourceCounter ResourceCounter) CountResourceOption { - return func(cr *CountResource) { - cr.resourceCounter = resourceCounter - } -} - -// NewResource - creates pool of connections to redis that manages a decrementing counter. -// If limit is less than zero then the counter is disabled and all methods are noops. -func NewResource( - cfg RedisConfig, - name string, - opt CountResourceOption, // to set the resource counter - opts ...ResourceOption, -) *CountResource { - - log := cfg.Log() - log.Debugf("'%s' Resource: '%s'", name, cfg.URL()) // assume at least one addr - - client, err := NewRedisClient(cfg) - if err != nil { - log.Panicf("bad redis config provided %v", err) - } - - r := &CountResource{ - Resource: &Resource{ - ClientContext: ClientContext{ - cfg: cfg, - name: name, - }, - resourceLimiter: defaultResourceLimiter, - refreshTTL: defaultTTL, - refreshCount: defaultRefreshCount, - tenantLimits: make(map[string]*tenantLimit), - client: client, - }, - resourceCounter: defaultResourceCounter, - } - - // set the resource counter - opt(r) - - for _, opt := range opts { - opt(r.Resource) - } - - return r -} - -func (cr *CountResource) Log() Logger { - return cr.Resource.Log() -} -func (cr *CountResource) Name() string { - return cr.Resource.name -} - -// Return adds 1 to counter -func (cr *CountResource) Return(ctx context.Context, tenantID string) error { - - log := cr.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("Return: '%s'", cr.countPath(tenantID)) - limit, err := cr.getLimit(ctx, tenantID) - if err != nil { - return err - } - - // if we are unlimited its fine - if limit < 0 { - return nil - } - - _, err = cr.getOperation(ctx, opIncCount, tenantID) - if err != nil { - // If we cannot increment the current counter then try and calculate the - // actual value.... Inefficient but should happen infrequently. - _, err = cr.initialise(ctx, tenantID, limit) - if err != nil { - return err - } - } - return nil -} - -// Consume subtracts 1 from counter -func (cr *CountResource) Consume(ctx context.Context, tenantID string) error { - - log := cr.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("Consume: '%s'", cr.countPath(tenantID)) - limit, err := cr.getLimit(ctx, tenantID) - if err != nil { - return err - } - - if limit <= 0 { - return nil - } - - _, err = cr.getOperation(ctx, opDecCount, tenantID) - if err != nil { - // If we cannot decrement the current counter then try and calculate the - // actual value.... Inefficient but should happen infrequently. - _, err = cr.initialise(ctx, tenantID, limit) - if err != nil { - return err - } - } - return nil -} - -// GetLimit gets the current limit -func (cr *CountResource) GetLimit() int64 { - return cr.limit -} - -// Limited returns true if the limit counter is enabled. The current limit -// is retrieved from upstream if necessary using the Limiter method. This happens -// when the current TTL parameters are exceeded. -func (cr *CountResource) Limited(ctx context.Context, tenantID string) bool { - log := cr.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("Limited: '%s'", cr.countPath(tenantID)) - limited := true - - var limits *tenantLimit - limits, ok := cr.tenantLimits[tenantID] - if !ok { - log.Debugf("Create tenantLimits %s", tenantID) - cr.tenantLimits[tenantID] = &tenantLimit{ - lastRefreshTime: time.Now(), - } - limits = cr.tenantLimits[tenantID] - } - limits.lastRefreshCount++ - elapsed := time.Since(limits.lastRefreshTime) - - // first attempt to get the current limit for the specific tenant - limit, err := cr.getLimit(ctx, tenantID) - if err != nil { - // for now if we can't find the limit we set to unlimited? - return false - } - - // stash the limit - cr.limit = limit - - // we do not need to pull from upstream the local limit will do - log.Debugf("elapsed %v refreshTTL %v lastRefreshCount %d refreshCount %d", - elapsed, cr.refreshTTL, limits.lastRefreshCount, cr.refreshCount, - ) - if elapsed < cr.refreshTTL && limits.lastRefreshCount < cr.refreshCount { - log.Debugf("TTL is still ok %s %d", tenantID, limit) - return limit >= 0 - } - - // pull from upstream - log.Infof("Get limit from tenancies service %s %s", cr.name, tenantID) - newLimit, err := cr.resourceLimiter(ctx, cr.Resource, tenantID) - if err != nil { - log.Infof("Unable to get upstream limit %s: %v", cr.name, err) - // return whatever the old limit was, as we can't get hold of the new limit - return limit >= 0 - } - - // reset the refresh triggers - log.Debugf("Reset the TTL triggers") - limits.lastRefreshCount = 0 - limits.lastRefreshTime = time.Now() - cr.limit = newLimit - - // check if the new limit is the same as the old limit - if newLimit == limit { - log.Infof("Limit has not changed %s %s", cr.name, tenantID) - return limit >= 0 - } - - log.Infof("new limit now %d (from %d)", newLimit, limit) - - // check if the new limit is unlimited - if newLimit == -1 { - limited = false - } - - // if we go from unlimited to limited we need to populate the counter - // if we decrease the caps limit we need to re-populate the counter - if (limit == -1 && newLimit >= 0) || (newLimit < limit && newLimit != -1) { - log.Infof("Populate counter %d (from %d)", newLimit, limit) - _, err = cr.initialise(ctx, tenantID, newLimit) - if err != nil { - log.Infof("reset count failure: %v", err) - } - } - - // set the new limit - log.Debugf("Set Redis %s %d", tenantID, limit) - err = cr.setOperation(ctx, opSetLimit, tenantID, newLimit) - if err != nil { - log.Infof("failed to set new limit: %v", err) - // return whatever the old limit was, as we can't set the new limit - return limit >= 0 - } - - return limited - -} - -// initialise - sets counter with initial value if it does not exist or is -// (temporarily inaccessible). -// Subtracts current number of resources determined by executing the Counter Method. -func (cr *CountResource) initialise(ctx context.Context, tenantID string, limit int64) (int64, error) { - log := cr.Log().FromContext(ctx) - defer log.Close() - - var err error - - log.Debugf("Initialise: '%s'", cr.countPath(tenantID)) - currentCount, err := cr.resourceCounter(ctx) - if err != nil { - log.Infof("currentCount failure: %v", err) - return int64(0), err - } - log.Debugf("Redis Resource Current Count: '%s' %d", cr.countPath(tenantID), currentCount) - var count int64 - if currentCount >= limit { - count = 0 - } else { - count = limit - currentCount - } - - err = cr.nakedWrite(ctx, count, tenantID) - // Log but ignore SET failure as we have enough info to enforce the limit without - // REDIS. - if err != nil { - log.Infof("Redis Resource SET failure: %v", err) - } - log.Debugf("Resource Initialised: '%s' %d", cr.countPath(tenantID), count) - - return count, nil -} - -// Available gets current value of counter and adjusts limit if required. -// An error is returned when either the redis counter cannot be read or this -// counter is disabled. -func (cr *CountResource) Available(ctx context.Context, tenantID string) (int64, error) { - log := cr.Log().FromContext(ctx) - defer log.Close() - - var err error - - log.Debugf("Available: '%s'", cr.countPath(tenantID)) - limit, err := cr.getLimit(ctx, tenantID) - if err != nil { - return 0, err - } - - count, err := cr.getOperation(ctx, opGetCount, tenantID) - if err != nil { - // If we cannot get the current counter then try and calculate the - // actual value.... Inefficient but should happen infrequently. - count, err = cr.initialise(ctx, tenantID, limit) - if err != nil { - return int64(0), err - } - } - log.Infof("Counter: %d", count) - - if count > 0 { - return count, nil - } - - // count is now zero so check the actual number of resources and adjust if necessary. - newLimit, err := cr.RefreshLimit(ctx, tenantID) - if err != nil { - log.Infof("reset count failure: %v", err) - newLimit = limit - } - count, err = cr.initialise(ctx, tenantID, newLimit) - if err != nil { - log.Infof("reset count failure: %v", err) - } - return count, nil -} - -// ReadOnlyAvailable -// -// Returns the number of available units of this resource. Attempts the fast path first, of getting -// from the cache directly. Will attempt to re-initialise the count if its inaccessible or 0. -func (cr *CountResource) ReadOnlyAvailable(ctx context.Context, tenantID string) (int64, error) { - log := cr.Log().FromContext(ctx) - defer log.Close() - - var err error - - log.Debugf("ReadOnlyAvailable: '%s'", cr.countPath(tenantID)) - // Pure read operation from the cache - will usually succeed and return a non-zero value. - count, err := cr.getOperation(ctx, opGetCount, tenantID) - if err != nil || count == 0 { - if err != nil { - log.Infof("error getting count from Redis: %v", err) - } - - // An error might indicate that the key wasn't available in the cache, so we use the - // full-fat method to initialise it. We also want to do this if the count has hit 0, since - // the user may have paid to increase their cap for this resource. - - log.Infof("ReadOnlyAvailable: Insufficient resource: Delegating to Available") - return cr.Available(ctx, tenantID) - } - - log.Debugf("ReadOnlyAvailable: Counter: %d", count) - return count, nil -} diff --git a/redis/docs.go b/redis/docs.go deleted file mode 100644 index b6a15a6..0000000 --- a/redis/docs.go +++ /dev/null @@ -1,46 +0,0 @@ -package redis - -// There are 3 interfaces in this package: -// -// 1. hashstore - caching of entities -// 2. count limits - limit the number of entities allowed per tenant. -// 3. size limits - limit the size of an entity per tenant. -// -// 1. Hashstore -// -// Caches entities using a key/value store. Get, Set and Delete methods -// are available. -// -// 2. Count Limits -// -// Handles counting of resources and limiting the number of instances to a global -// value. -// -// The underlying counter is stored in REDIS using a serialised counter that -// is decremented whenever an instance of the entity is created. If the counter -// reaches zero, permission to create the entity is denied - Available() returns -// the number of available slots which in this case will be zero. -// -// Additionally the limit is fetched using a user-supplied method. Suitable logic to -// adjust the counter value when the limit changes is supplied in the initialise() -// method. Persistence is achieved by re-initialising when an error occurs accessing -// REDIS (in case the REDIS service is momentarily unavailable). Reiniialising consists -// of using a user supplied Counter() method to get the current number of entities. -// -// If the current counter value is unavailable from REDIS and we are unable to -// calculate the actual value using the Counter Method then we fail 'closed'. -// -// When creating an entity the caps limits is checked. There are three possibilities: -// -// 1. No limit is imposed i.e. 'unlimited' in which case the caps limit checking -// is not used. -// 2. A limit is in place and the current number of entities is below the limit. In -// which case the entity is created and - if no error occurs - the counter is -// decremented. -// 3. A limit is in place and the current number of entities is at the limit. In -// which case a 402 code is returned without attempting to create the entity. -// -// 3. Size Limits -// -// Limits the size of an entity - for example the maximum size of a blob. -// diff --git a/redis/error.go b/redis/error.go deleted file mode 100644 index 657566d..0000000 --- a/redis/error.go +++ /dev/null @@ -1,44 +0,0 @@ -package redis - -import ( - "errors" - "fmt" -) - -var ( - ErrNoTenantID = errors.New("no tenant ID supplied") - ErrRedisClose = errors.New("redis close error") - ErrRedisConnect = errors.New("redis connect error") - ErrRedisCounterDisabled = errors.New("redis counter disabled") - ErrRedisDial = errors.New("redis dial error") - ErrRedisDo = errors.New("redis do error") - ErrRedisSend = errors.New("redis send error") -) - -func NoTenantIDError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrNoTenantID, name, err) -} - -func CloseError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisClose, name, err) -} - -func ConnectError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisConnect, name, err) -} - -func DialError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisDial, name, err) -} - -func DisabledError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisCounterDisabled, name, err) -} - -func DoError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisDo, name, err) -} - -func SendError(err error, name string) error { - return fmt.Errorf("%s %s: %w", ErrRedisSend, name, err) -} diff --git a/redis/hashcache.go b/redis/hashcache.go deleted file mode 100644 index cc10453..0000000 --- a/redis/hashcache.go +++ /dev/null @@ -1,206 +0,0 @@ -package redis - -import ( - "context" - "encoding/hex" - "encoding/json" - "fmt" - "time" - - "github.com/go-redis/redis/v8" - otrace "github.com/opentracing/opentracing-go" -) - -// HashCache uses redis optimistic locking and hash store to cache strings -// safely and prevents race conditions between Get/Set and Delete -// https://redis.io/topics/transactions#optimistic-locking-using-check-and-set -type HashCache struct { - Cfg RedisConfig - // the client is long lived and has its own internal pool. There is no strict need to "close" - client RedisClient - expiryTimeoutSeconds int64 - cacheMisses int64 - cacheHits int64 -} - -func NewHashCache(cfg RedisConfig, expiryTimeoutSeconds int64) (*HashCache, error) { - log := cfg.Log() - - log.Debugf("Redis NewHashStore") - - client, err := NewRedisClient(cfg) - if err != nil { - return nil, err - } - - c := &HashCache{ - Cfg: cfg, - client: client, - } - c.expiryTimeoutSeconds = expiryTimeoutSeconds - return c, nil -} - -func (c *HashCache) Log() Logger { - return c.Cfg.Log() -} - -func (c *HashCache) Close() error { - c.Log().Debugf("HashCache Close") - if c.client == nil { - return nil - } - err := c.client.Close() - c.client = nil - return err -} - -func (c *HashCache) Delete(ctx context.Context, name string) error { - log := c.Log().FromContext(ctx) - defer log.Close() - - key := fmt.Sprintf("%s:%s", c.Cfg.Namespace(), name) - log.Debugf("Delete: %s", key) - span, ctx := otrace.StartSpanFromContext(ctx, "redis.hashcache.Del") - defer span.Finish() - - // DEL deletes the hash (HDEL would delete a field) - _, err := c.client.Del(ctx, key).Result() - if err != nil { - return err - } - return nil -} - -type CachedReader func(results any) error - -// CachedRead returns the results from the cache if available. Otherwise the -// reader is invoked and its results are returned instead. The results from the -// reader, if invoked, are cached. This process is transactional. if the (name, -// field) is modified on another connection during the span from read to set, the -// set is not applied. -// -// A note on err handling: -// - If we fail to read the value from the cache (for any reason) the reader -// callback is invoked once (and only once) regardless of any other error. -// - if the reader returns an err that is *always* returned to the caller. this -// is important as otherwise there is no way to distinguish between a failed -// read and an empty response from the cache or the db -// - errors writing to or fetching from the cache are treated as transient and -// are logged but not returned (persistent cache read/write errors will -// surface as performance issues or log spam) -// - errors marshalling or unmarshaling values for the cache are returned to the -// caller provided a reader error has not occurred. -func (c *HashCache) CachedRead(ctx context.Context, results any, name, field string, reader CachedReader) error { - log := c.Log().FromContext(ctx) - defer log.Close() - - key := fmt.Sprintf("%s:%s", c.Cfg.Namespace(), name) - - cacheUpdate := func(tx *redis.Tx, update any) error { - span, spanCtx := otrace.StartSpanFromContext(ctx, "redis.hashcache.CachedRead.cacheUpdate.TxPipelined") - defer span.Finish() - - // transactionaly update the cache - b, err := json.Marshal(update) - if err != nil { - log.Infof("Unable to marshal cache update: %v", err) - return err - } - - // errors from here are considered transient and logged - - // Operation is committed only if the watched keys remain unchanged. - _, err = tx.TxPipelined(spanCtx, func(pipe redis.Pipeliner) error { - pipe.HSet(spanCtx, key, field, b) - pipe.Expire(spanCtx, key, time.Duration(c.expiryTimeoutSeconds)*time.Second) - return nil - }) - if err != nil { - log.Infof("Unable to set result to cache: %v", err) - } - log.Debugf("update cache. key: %s, field: %s, value: %v", key, field, hex.EncodeToString(b)) - return nil - } - - readerCalledOnce := false - // In practice we see Watch return nil and yet transact not be called. In - // this situation we need to guarantee reader is invoked - cacheHit := false - var readerErr error // always returned to the caller - - // Note: we have switched from redigo to go-redis. go-redis implements - // connection pooling, is cluster aware, and has builtin support for - // pipelining and transactions. - transact := func(tx *redis.Tx) error { - span, spanCtx := otrace.StartSpanFromContext(ctx, "redis.hashcache.CachedRead.transact.HGet") - cachedResults, err := tx.HGet(spanCtx, key, field).Result() - span.Finish() - if err != nil { - c.cacheMisses++ - log.Debugf("unable to read from cache. key: %s, field: %s, expiry: %v, err: %v", key, field, c.expiryTimeoutSeconds, err) - - readerErr = reader(results) - readerCalledOnce = true // regardless of err, we have called it exactly once - if readerErr != nil { - log.Infof("reader failed getting results (for miss): %v", err) - return readerErr - } - return cacheUpdate(tx, results) - } - - fbytes := []byte(cachedResults) - if err = json.Unmarshal(fbytes, &results); err != nil { - c.cacheMisses++ - log.Infof("unable to unmarshall cached result, err: %v", err) - readerErr = reader(results) - readerCalledOnce = true // regardless of err, we have called it exactly once - if readerErr != nil { - log.Infof("reader failed getting results (for corrupt entry): %v", err) - return readerErr - } - - // with the readerErro dealt with, the only err returned by the - // cacheUpdate is a marshalling err. We are already handling an - // unmarshal error here so we return that in preference. - _ = cacheUpdate(tx, results) - return err - } - c.cacheHits++ - cacheHit = true - log.Debugf("returning results from cache, hit rate %.2f%% (hits %d misses %d)", - (100.0*float64(c.cacheHits))/float64(c.cacheHits+c.cacheMisses), c.cacheHits, c.cacheMisses) - return nil - } - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.hashcache.CachedRead.Watch") - defer span.Finish() - err := c.client.Watch(ctx, transact, key) - if err != nil || !cacheHit { - - // We need to ensure the reader is called exactly once. And if it errors - // we must return that error. Otherwise, we return the redis cache - // response err. In practice we have seen Watch return nil without - // invoking transact at all, so we need to check explicitly for cachHit - // in the non err case. - if err != nil { - log.Infof("watched transaction failed: %v", err) - } - if err == nil { - log.Infof("go-redis watch returned nil error without a successful cache hit") - } - if readerCalledOnce { - if readerErr != nil { - return readerErr - } - return err - } - readerErr = reader(results) - } - - if readerErr != nil { - log.Infof("reader failed getting results (for failed watch): %v", readerErr) - } - - return readerErr -} diff --git a/redis/jsonresource.go b/redis/jsonresource.go deleted file mode 100644 index 6303d9e..0000000 --- a/redis/jsonresource.go +++ /dev/null @@ -1,104 +0,0 @@ -package redis - -import ( - "context" - "encoding/json" - "fmt" - - otrace "github.com/opentracing/opentracing-go" -) - -// NewJsonResource constructs a new instance of JsonResource, given the configuration. -// - name: name of the resource -// - resType: the general type of resource, mostly to help with organisation -// - cfg: the Redis cluster configuration to use -func NewJsonResource(name string, cfg RedisConfig, resType string) (*JsonResource, error) { - client, err := NewRedisClient(cfg) - if err != nil { - return nil, fmt.Errorf("unable to create redis client: %w", err) - } - - return &JsonResource{ - ClientContext: ClientContext{ - cfg: cfg, - name: name, - }, - client: client, - keyPrefix: fmt.Sprintf("%s/%s/%s", cfg.Namespace(), resType, name), - }, nil -} - -// JsonResource is a Redis resource holding an object in JSON -type JsonResource struct { - ClientContext - client Client - keyPrefix string -} - -// URL gets the configured Redis URL -func (r *JsonResource) URL() string { - return r.cfg.URL() -} -func (r *JsonResource) Log() Logger { - return r.cfg.Log() -} - -// Name gets the name of the resource -func (r *JsonResource) Name() string { - return r.name -} - -// Key gets the full resource identification key -func (r *JsonResource) Key(tenantID string) string { - return r.keyPrefix + "/" + tenantID -} - -// Set takes a JSON-serializable value, marshals it, and stores it for the given tenantID -func (r *JsonResource) Set(ctx context.Context, tenantID string, value any) error { - log := r.Log().FromContext(ctx) - defer log.Close() - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.resource.setOperation.Set") - defer span.Finish() - - jsonBytes, err := json.Marshal(value) - if err != nil { - return err - } - - _, err = r.client.Set(ctx, r.Key(tenantID), string(jsonBytes), redisDefaultTTL).Result() - if err != nil { - return err - } - - log.Debugf("Set: set resource '%s' to '%s'", r.Key(tenantID), value) - return nil -} - -// Get reads the resource for the given tenantID, and unmarshals it into target (which must be a -// pointer to a suitable empty struct.) -func (r *JsonResource) Get(ctx context.Context, tenantID string, target any) error { - log := r.Log().FromContext(ctx) - defer log.Close() - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.resource.getOperation.Do") - defer span.Finish() - - result, err := r.client.Do(ctx, "GET", r.Key(tenantID)).Result() - if err != nil { - log.Infof("Get: error getting result for %s: %v", r.Key(tenantID), err) - return err - } - - resultStr, ok := result.(string) - if !ok { - return fmt.Errorf("could not interpret result for: %s as string", r.Key(tenantID)) - } - - err = json.Unmarshal([]byte(resultStr), target) - if err != nil { - return err - } - - return nil -} diff --git a/redis/jsonresource_test.go b/redis/jsonresource_test.go deleted file mode 100644 index 44571a6..0000000 --- a/redis/jsonresource_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package redis - -import ( - "context" - "testing" - - "github.com/alicebob/miniredis/v2" - "github.com/datatrails/go-datatrails-common/logger" - "github.com/go-redis/redis/v8" - "github.com/stretchr/testify/require" -) - -// NewTestResource sets up a fresh instance of miniredis and returns a configured JsonResource -func NewTestResource(log Logger, t *testing.T) *JsonResource { - mr := miniredis.RunT(t) - c := redis.NewClient(&redis.Options{Addr: mr.Addr()}) - return &JsonResource{ - client: c, - ClientContext: ClientContext{ - cfg: &clusterConfig{log: log}, - name: "test-json-resource", - }, - keyPrefix: "resource-prefix", - } -} - -type TestStruct struct { - Foo string - Bar int64 -} - -// TestRoundtrip ensures we can Get a previously Set value, marshalling and unmarshalling as needed. -func TestRoundtrip(t *testing.T) { - logger.New("NOOP") - defer logger.OnExit() - - resource := NewTestResource(logger.Sugar, t) - - setErr := resource.Set(context.TODO(), "tenant/1", &TestStruct{Foo: "hello world", Bar: 1337}) - require.NoError(t, setErr) - - result := TestStruct{} - getErr := resource.Get(context.TODO(), "tenant/1", &result) - require.NoError(t, getErr) - - require.Equal(t, "hello world", result.Foo) - require.Equal(t, int64(1337), result.Bar) -} - -// TestExpectedUnmarshalError ensures that Get errors if it cannot unmarshal into the provided -// struct. -func TestExpectedUnmarshalError(t *testing.T) { - logger.New("NOOP") - defer logger.OnExit() - - resource := NewTestResource(logger.Sugar, t) - - setErr := resource.Set(context.TODO(), "tenant/1", &TestStruct{Foo: "hello world", Bar: 1337}) - require.NoError(t, setErr) - - type OtherTestStruct struct { - Foo int64 - Bar string - } - - result := OtherTestStruct{} - getErr := resource.Get(context.TODO(), "tenant/1", &result) - require.Error(t, getErr) -} diff --git a/redis/logger.go b/redis/logger.go deleted file mode 100644 index 3b0eee5..0000000 --- a/redis/logger.go +++ /dev/null @@ -1,7 +0,0 @@ -package redis - -import ( - "github.com/datatrails/go-datatrails-common/logger" -) - -type Logger = logger.Logger diff --git a/redis/monotonic.go b/redis/monotonic.go deleted file mode 100644 index c14f871..0000000 --- a/redis/monotonic.go +++ /dev/null @@ -1,366 +0,0 @@ -package redis - -// A monotonically incressing counter which is periodically refreshed from a -// source known to also be monotonic. Intended for tracking organization wallet -// nonces where the reset calls the ledger for the latest nonce. We use a -// refresh on expiry pattern using 'set if not exist' to make this reliable. -// -// This primitive is intended to support ethereum nonce management. To support -// that use case we implement the following: -// -// 1. The counter is not explicitly initialised -// 2. The first increment will fail because the value does not exist -// 3. We use SETNX to 'set if not exists' so only the first attempt to initialise the counter will succede -// 4. When the value expires, we are back at 1. again -// 5. The setter that is successful returns the value set to the caller as the 'next nonce' -// 6. All other setters call incr again. If this incr indicates the value was -// missing (again) an error is returned -// -// 7. For the specific case of nonce management, The last piece of the puzle is -// once the receipt is successfully obtained a conditional set is issued: set -// current = completed.nonce IF comleted > current -// -// So that we can detect 'not initialised' from INCR we implement the standard -// INCR as a lua script but error in the case where the value doesn't exist. We -// call this INCREX. The implementation is closely derived from go-redis docs -// -// re 6. all other setters call incr again for nonce management: the client will -// make one attempt to refresh. For many racing clients, Exactly one will -// succeede and return the set value as the current nonce. The remainder will -// each do one further call to INCREX and return the result. Each of those -// clients gets a distinct and sequential nonce. If that second INCREX fails it -// suggests the item expired very quickly or was deleted. In either case we -// return error to the caller. The caller can then retry or not at their -// discression. -// -import ( - "context" - "fmt" - "time" - - "github.com/go-redis/redis/v8" - - otrace "github.com/opentracing/opentracing-go" -) - -const ( - incrNExNotFound = "not-found" - - // This TTL is our guard against sporadic nonce gaps. These will occur for - // example if the submition of the transaction to the ledger fails for - // mundane reasons *after* the nonce is incremented. We have two tacticts - // for managing nonce gaps to avoid arbitrary delays: - // 1. The tx issuer spots the error and issues an imediate DEL causing all - // clients to attepmt to fill the gap - // 2. The TTL declared here ensures in the face of a crashed client the gap - // still gets filled withoutt having to wait for the next archivist event on - // the org wallet. - // - // Its not possible to both parallelise the transaction submission *and* - // guarantee no nonce duplicates or gaps. The duplicates need not be a - // problem if managed properly - they simply get dropped by the nodes. The - // nonce-to-low error is completley managable provided transaction - // preparation is de-coupled from transaction signing. On nonce-to-low, - // simply get a new nonce and try again. - monotonicTTL = time.Second * 30 -) - -// INCREX lua script, derived from the documented example of regular INCR -// here: https://redis.uptrace.dev/guide/lua-scripting.html#redis-script -// -// In order to detect when the counter value is absent (or expired) we -// define a variant of INCR that errors when the value is absent. See the -// file level comment for how this fits into the broader picture of nonce -// tracking. go-redis automatcailly uses EVALHASH & EVAL to ensure efficient -// management of the script. - -// Note: this one is careful to avoid resetting the ttl on set -var incrNEx = redis.NewScript(` -local key = KEYS[1] -local change = tonumber(ARGV[1]) - -if change < 0 then - return {err = "increment only"} -end - -local value = redis.call("GET", key) -if not value then - return {err = "not-found"} -end - -value = tonumber(value) + change -redis.call("SET", key, value, "KEEPTTL") - -return value -`) - -// Note: this one is careful to avoid resetting the ttl on set -var setGT = redis.NewScript(` -local key = KEYS[1] -local change = tonumber(ARGV[1]) - -local value = redis.call("GET", key) - -if not value then - redis.call("SET", key, change) - return change -end - -value = tonumber(value) - -if value >= change - return value -end - -redis.call("SET", key, change, "KEEPTTL") - -return value -`) - -type CountRefresh = func(context.Context, string) (int64, error) - -type ScriptRunner interface { - Run(ctx context.Context, c redis.Scripter, keys []string, args ...any) *redis.Cmd -} - -type ScripterClient interface { - Scripter - SetNX(ctx context.Context, key string, value any, expiration time.Duration) *redis.BoolCmd - Del(ctx context.Context, keys ...string) *redis.IntCmd -} - -type MonotonicOption func(*Monotonic) - -type Monotonic struct { - ClientContext - client ScripterClient - resetPeriod time.Duration - refresh CountRefresh - incrNExRunner ScriptRunner // assumed to be incrEx (above) - setGTRunner ScriptRunner // assumed to be setGT (above) -} - -func NewMonotonic( - cfg RedisConfig, - name string, - opts ...MonotonicOption, -) Monotonic { - - log := cfg.Log() - - log.Debugf("'%s' Resource: '%s'", name, cfg.URL()) // assume at least one addr - - client, err := NewRedisClient(cfg) - if err != nil { - log.Panicf("bad redis config provided %v", err) - } - - c := Monotonic{ - ClientContext: ClientContext{ - cfg: cfg, - name: name, - }, - client: client, - resetPeriod: monotonicTTL, - incrNExRunner: incrNEx, - setGTRunner: setGT, - } - - for _, opt := range opts { - opt(&c) - } - - return c -} - -func (c *Monotonic) SetRefresher(refresh CountRefresh) CountRefresh { - prev := c.refresh - c.refresh = refresh - return prev -} - -func (c *Monotonic) Name() string { - return c.name -} - -func (c *Monotonic) Log() Logger { - return c.cfg.Log() -} -func (c *Monotonic) URL() string { - return c.cfg.URL() -} - -func (c *Monotonic) IncrN(ctx context.Context, tenantIDOrWallet string, n int64) (int64, error) { - log := c.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("IncrN %s %d", tenantIDOrWallet, n) - n, err := c.incrNEx(ctx, tenantIDOrWallet, n) - log.Debugf("IncrN = %d: err?=%v", n, err) - return n, err -} -func (c *Monotonic) SetGT(ctx context.Context, tenantIDOrWallet string, cas int64) (int64, error) { - log := c.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("SetGT %s %d", tenantIDOrWallet, cas) - n, err := c.setGT(ctx, tenantIDOrWallet, cas) - log.Debugf("SetGT: err?=%v", err) - return n, err -} - -func (c *Monotonic) countPath(tenantIDOrWallet string) string { - return "Redis monotonic: " + c.URL() + c.countKey(tenantIDOrWallet) -} - -func (c *Monotonic) countKey(tenantIDOrWallet string) string { - return c.cfg.Namespace() + "/counters/" + c.name + "/" + tenantIDOrWallet + "/" + "count" -} - -func (c *Monotonic) Del(ctx context.Context, tenantIDOrWallet string) error { - log := c.Log().FromContext(ctx) - defer log.Close() - - key := c.countKey(tenantIDOrWallet) - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.Del") - defer span.Finish() - log.Debugf("Del %s", tenantIDOrWallet) - _, err := c.client.Del(ctx, key).Result() - if err != nil { - log.Debugf("Redis monotonic: Del %s: %v", key, err) - return err - } - log.Debugf("Del %s ok", tenantIDOrWallet) - return nil -} - -// When used for tracking account nonces, this allows delayed transactions to -// fill gaps and re-sync the nonce cache -func (c *Monotonic) setGT(ctx context.Context, tenantIDOrWallet string, cas int64) (int64, error) { - log := c.Log().FromContext(ctx) - defer log.Close() - - key := c.countKey(tenantIDOrWallet) - path := c.countPath(tenantIDOrWallet) - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.setGT(script)") - defer span.Finish() - - // count is guaranteed to be the higher of cas or the current value. 'cas' means compare and set. - count, err := c.setGTRunner.Run( - ctx, c.client, []string{key}, cas).Int64() - if err != nil { - log.Debugf("Redis monotonic: setGT %s: %v", path, err) - return 0, err - } - // Happy path - return count, nil -} - -// setNX runs a `SETNX` operation for redis. -func (c *Monotonic) setNX(ctx context.Context, tenantIDOrWallet string, arg int64) (bool, error) { - log := c.Log().FromContext(ctx) - defer log.Close() - - value := parseArg(arg) - // only pass string arguments to redis - - // find the correct key and path - key := c.countKey(tenantIDOrWallet) - path := c.countPath(tenantIDOrWallet) - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.SetNX") - defer span.Finish() - - log.Debugf("SetNX %s %v", tenantIDOrWallet, arg) - result, err := c.client.SetNX(ctx, key, value, c.resetPeriod).Result() - if err != nil { - log.Debugf("Redis monotonic: NOT SET %s: %s: %v", path, value, err) - return false, err - } - - log.Debugf("Redis monotonic: SET %s: %s", path, value) - return result, nil -} - -func (c *Monotonic) incrNEx(ctx context.Context, tenantIDOrWallet string, n int64) (int64, error) { - log := c.Log().FromContext(ctx) - defer log.Close() - - key := c.countKey(tenantIDOrWallet) - path := c.countPath(tenantIDOrWallet) - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.INCREX(script)") - defer span.Finish() - - count, err := c.incrNExRunner.Run(ctx, c.client, []string{key}, n).Int64() - if err == nil { - // Happy path - return count, nil - } - - // Deal with count refresh/sync - if err.Error() != incrNExNotFound { - // actual error rather than the 'not-found' signal - err = fmt.Errorf("redis monotonic: INCRNEX failed %s: %w", path, err) - log.Debugf("%v", err) - - return 0, err - } - - // Value is missing or has never been set. ask for the latest value - spanRefresh, ctx := otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.INCRNEX(script) count refresh") - defer spanRefresh.Finish() - // This means the count did not exist or expired, trigger a refresh - log.Debugf("Redis monotonic: expired or not initialised - refreshing %s: %v", tenantIDOrWallet, err) - count, err = c.refresh(ctx, tenantIDOrWallet) - if err != nil { - log.Debugf("Redis monotonic: refresh %s: %v", path, err) - return 0, err - } - - // issue SETNX. Only one attempt here will succeed assuming: - // 1. nobody deletes the key manually - // 2. the expiry isn't rediculously small (as long as its fairly big it doesnt matter if occasionally this happens) - // - // In the case where a nonce gap is being force filled by a client, DEL is - // issued manually, so there is a razor thin chance (meaning it _will_ - // happen from time to time) to instances will get nonce duplicates. Only - // one of those transactions sharing that duplicate nonce will mine - - // Now we have the current value we add our n, as we need the effect of the - // operation to be consisten regardless of whether we refreshed. (The - // current use of this is for claiming 1 or more nonces). - count += n - - ok, err := c.setNX(ctx, tenantIDOrWallet, count) - if err != nil { - // This is a straight up error, the caller sees this as a failed attempt to aquire the count current value - log.Debugf("Redis monotonic: refresh setNX (ignoring error) %s: %v", path, err) - return 0, err - } - if ok { - // We are the only client to set the value, it is the correct current - // value to return to the caller. And we successfully added the 'n' - log.Debugf("Redis monotonic: refresh setNX ok *(re)initialised* %s: count=%d, added=%d, was=%d", path, count, n, count-n) - return count, nil - } - - // Ok, here we are one of the refresh race losers and the count already - // exists. We issue a single further incr . Here we are claiming our n. All - // callers, regardless of whether they re-initialised the counter, need - // their increment to be applied. If we get another not found (or any other - // error) the client should see this as a transient failure to read the - // count - - span, ctx = otrace.StartSpanFromContext(ctx, "redis.counter.setOperation.INCREX(script)(2)") - defer span.Finish() - count, err = c.incrNExRunner.Run(ctx, c.client, []string{key}, n).Int64() - if err == nil { - log.Debugf("Redis monotonic: refresh refresh loser ok %s: count=%d, added=%d, was=%d", path, count, n, count-n) - return count, nil - } - log.Debugf("Redis monotonic: refresh refresh loser error %s: %v", path, err) - return 0, err -} diff --git a/redis/redistore.go b/redis/redistore.go deleted file mode 100644 index fc9d839..0000000 --- a/redis/redistore.go +++ /dev/null @@ -1,240 +0,0 @@ -package redis - -// LICENSE MIT -// copied almost verbatim from https://github.com/rbcervilla/redisstore -// in order to add the session encryption present in boj/redistore (which -// needs redigo) - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/base32" - "encoding/gob" - "errors" - "io" - "net/http" - "strings" - "time" - - "github.com/go-redis/redis/v8" - "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" - otrace "github.com/opentracing/opentracing-go" -) - -const ( - sessionSize = 1024 * 16 - clientTimeout = 10 * time.Second - namespaceSeparator = ":" -) - -// GorillaStore stores gorilla sessions in Redis -// -//nolint:golint -type GorillaStore struct { - // Client to connect to redis - Client RedisClient - - Codecs []securecookie.Codec - // default Options to use when a new session is created - Options sessions.Options - MaxLength int - // key prefix with which the session will be stored - keyPrefix string - // key generator - keyGen KeyGenFunc - // session serialiser - serialiser SessionSerialiser -} - -// KeyGenFunc defines a function used by store to generate a key -type KeyGenFunc func() (string, error) - -// NewRedisStore returns a new RedisStore with default configuration -func NewRedisStore(cfg RedisConfig, keyPairs ...[]byte) (*GorillaStore, error) { - - client, err := NewRedisClient(cfg) - if err != nil { - return nil, err - } - - rs := &GorillaStore{ - Codecs: securecookie.CodecsFromPairs(keyPairs...), - Options: sessions.Options{ - Path: "/", - MaxAge: 86400 * 30, - }, - Client: client, - MaxLength: sessionSize, - keyPrefix: cfg.Namespace() + namespaceSeparator, - keyGen: generateRandomKey, - serialiser: GobSerialiser{}, - } - - ctx, cancel := context.WithTimeout(context.Background(), clientTimeout) - defer cancel() - - return rs, rs.Client.Ping(ctx).Err() -} - -// Get returns a session for the given name after adding it to the registry. -func (s *GorillaStore) Get(r *http.Request, name string) (*sessions.Session, error) { - return sessions.GetRegistry(r).Get(s, name) -} - -// New returns a session for the given name without adding it to the registry. -func (s *GorillaStore) New(r *http.Request, name string) (*sessions.Session, error) { - session := sessions.NewSession(s, name) - opts := s.Options - session.Options = &opts - session.IsNew = true - - c, err := r.Cookie(name) - if err != nil { - return session, nil - } - session.ID = c.Value - - ctx, cancel := context.WithTimeout(context.Background(), clientTimeout) - defer cancel() - - err = s.load(ctx, session) - if err == nil { - session.IsNew = false - } else if errors.Is(err, redis.Nil) { - err = nil // no data stored - } - return session, err -} - -// Save adds a single session to the response. -// -// If the Options.MaxAge of the session is <= 0 then the session file will be -// deleted from the store. With this process it enforces the properly -// session cookie handling so no need to trust in the cookie management in the -// web browser. -func (s *GorillaStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error { - - ctx, cancel := context.WithTimeout(context.Background(), clientTimeout) - defer cancel() - - // Delete if max-age is <= 0 - if session.Options.MaxAge <= 0 { - if err := s.delete(ctx, session); err != nil { - return err - } - http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options)) - return nil - } - - if session.ID == "" { - id, err := s.keyGen() - if err != nil { - return errors.New("redisstore: failed to generate session id") - } - session.ID = id - } - - ctx, cancel = context.WithTimeout(context.Background(), clientTimeout) - defer cancel() - - if err := s.save(ctx, session); err != nil { - return err - } - - http.SetCookie(w, sessions.NewCookie(session.Name(), session.ID, session.Options)) - return nil -} - -// KeyPrefix sets the key prefix to store session in Redis -func (s *GorillaStore) KeyPrefix(keyPrefix string) { - s.keyPrefix = keyPrefix -} - -// KeyGen sets the key generator function -func (s *GorillaStore) KeyGen(f KeyGenFunc) { - s.keyGen = f -} - -// Serialiser sets the session serialiser to store session -func (s *GorillaStore) Serialiser(ss SessionSerialiser) { - s.serialiser = ss -} - -// Close closes the Redis store -func (s *GorillaStore) Close() error { - return s.Client.Close() -} - -// save writes session in Redis -func (s *GorillaStore) save(ctx context.Context, session *sessions.Session) error { - - b, err := s.serialiser.Serialise(session) - if err != nil { - return err - } - if s.MaxLength != 0 && len(b) > s.MaxLength { - return errors.New("sessionStore: the value to store is too big") - } - span, ctx := otrace.StartSpanFromContext(ctx, "redis.redistore.Set") - defer span.Finish() - return s.Client.Set(ctx, s.keyPrefix+session.ID, b, time.Duration(session.Options.MaxAge)*time.Second).Err() -} - -// load reads session from Redis -func (s *GorillaStore) load(ctx context.Context, session *sessions.Session) error { - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.redistore.Set") - cmd := s.Client.Get(ctx, s.keyPrefix+session.ID) - span.Finish() - - if cmd.Err() != nil { - return cmd.Err() - } - - b, err := cmd.Bytes() - if err != nil { - return err - } - - return s.serialiser.Deserialise(b, session) -} - -// delete deletes session in Redis -func (s *GorillaStore) delete(ctx context.Context, session *sessions.Session) error { - return s.Client.Del(ctx, s.keyPrefix+session.ID).Err() -} - -// SessionSerialiser provides an interface for serialise/deserialise a session -type SessionSerialiser interface { - Serialise(s *sessions.Session) ([]byte, error) - Deserialise(b []byte, s *sessions.Session) error -} - -// Gob serialiser -type GobSerialiser struct{} - -func (gs GobSerialiser) Serialise(s *sessions.Session) ([]byte, error) { - buf := new(bytes.Buffer) - enc := gob.NewEncoder(buf) - err := enc.Encode(s.Values) - if err == nil { - return buf.Bytes(), nil - } - return nil, err -} - -func (gs GobSerialiser) Deserialise(d []byte, s *sessions.Session) error { - dec := gob.NewDecoder(bytes.NewBuffer(d)) - return dec.Decode(&s.Values) -} - -// generateRandomKey returns a new random key -func generateRandomKey() (string, error) { - k := make([]byte, 64) - if _, err := io.ReadFull(rand.Reader, k); err != nil { - return "", err - } - return strings.TrimRight(base32.StdEncoding.EncodeToString(k), "="), nil -} diff --git a/redis/resource.go b/redis/resource.go deleted file mode 100644 index f7824aa..0000000 --- a/redis/resource.go +++ /dev/null @@ -1,331 +0,0 @@ -package redis - -// Handles creating a limit. Use CountResource or SizeResource for specific limits. -// -// The underlying limit is stored in REDIS. The limit can be retrieved using 'getLimit()', -// which first attempts to get the limit via REDIS, then upstream if REDIS is unavailable. - -import ( - "context" - "errors" - "strconv" - "strings" - "time" - - "github.com/go-redis/redis/v8" - otrace "github.com/opentracing/opentracing-go" -) - -const ( - // fetch upstream limit if has not been fetched for at least - // 1 millisecond or for the last 0 creation events. - // These settings are most inefficient but can be overridden - // when creating the client. - defaultTTL = 1 * time.Millisecond - defaultRefreshCount = 0 - redisDefaultTTL = time.Second * 1800 - - opSetCount = "SET_Count" - opGetCount = "GET_Count" - opIncCount = "INCR_Count" - opDecCount = "DECR_Count" - - opSetLimit = "SET_Limit" - opGetLimit = "GET_Limit" -) - -var ( - ErrUnknownRedisOperation = errors.New("unknown redis operation") -) - -// ResourceLimiter is a function which accepts a resource and retrieves the latest -// caps limit setting from upstream. -type ResourceLimiter = func(context.Context, *Resource, string) (int64, error) - -func defaultResourceLimiter(ctx context.Context, r *Resource, tenantID string) (int64, error) { - return int64(0), nil -} - -type tenantLimit struct { - lastRefreshTime time.Time - lastRefreshCount int64 -} - -// defines a redis client -type Client interface { - // Do executes a Do command to redis - Do(ctx context.Context, args ...any) *redis.Cmd - Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd - SetNX(ctx context.Context, key string, value any, expiration time.Duration) *redis.BoolCmd - Close() error -} - -type ClientContext struct { - cfg RedisConfig - name string -} - -type Resource struct { - ClientContext - client Client - resourceLimiter ResourceLimiter - tenantLimits map[string]*tenantLimit - refreshTTL time.Duration - refreshCount int64 -} - -func (r *Resource) Log() Logger { - return r.cfg.Log() -} -func (r *Resource) URL() string { - return r.cfg.URL() -} -func (r *Resource) Name() string { - return r.name -} - -type ResourceOption func(*Resource) - -func WithResourceLimiter(resourceLimiter ResourceLimiter) ResourceOption { - return func(r *Resource) { - r.resourceLimiter = resourceLimiter - } -} -func WithRefreshCount(refreshCount int64) ResourceOption { - return func(r *Resource) { - r.refreshCount = refreshCount - } -} -func WithRefreshTTL(refreshTTL time.Duration) ResourceOption { - return func(r *Resource) { - r.refreshTTL = refreshTTL - } -} - -func (r *Resource) countPath(tenantID string) string { - return "Redis Resource " + r.URL() + r.countKey(tenantID) -} - -func (r *Resource) countKey(tenantID string) string { - return r.cfg.Namespace() + "/limits/" + tenantID + "/" + r.name + "/" + "count" -} - -func (r *Resource) limitPath(tenantID string) string { - return "Redis Resource " + r.URL() + r.limitKey(tenantID) -} - -func (r *Resource) limitKey(tenantID string) string { - return r.cfg.Namespace() + "/limits/" + tenantID + "/" + r.name + "/" + "limit" -} - -// setOperation runs a specific `SET` operation for redis. -func (r *Resource) setOperation(ctx context.Context, operation string, tenantID string, arg int64) error { - log := r.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("resource operation %s", operation) - - // only pass string arguments to redis - strArg := parseArg(arg) - - // find the correct key and path - var key, path string - switch operation { - case opSetCount: - key = r.countKey(tenantID) - path = r.countPath(tenantID) - case opSetLimit: - key = r.limitKey(tenantID) - path = r.limitPath(tenantID) - default: - return ErrUnknownRedisOperation - } - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.resource.setOperation.Set") - defer span.Finish() - - // Don't care about the return value - _, err := r.client.Set(ctx, key, strArg, redisDefaultTTL).Result() - if err != nil { - return err - } - - log.Debugf("Redis Resource %s: '%s'", operation, path) - return err -} - -// getOperation runs a specific getter operation for redis. This includes -// -// `INCR`, `DECR` and `GET`. -func (r *Resource) getOperation(ctx context.Context, operation string, tenantID string) (int64, error) { - log := r.Log().FromContext(ctx) - defer log.Close() - - // process the operation, expect format to be 'OP_ID' eg. 'GET_limit' - op := strings.Split(operation, "_")[0] - - // find the correct key and path - var key, path string - switch operation { - case opGetCount, opDecCount, opIncCount: - key = r.countKey(tenantID) - path = r.countPath(tenantID) - case opGetLimit: - key = r.limitKey(tenantID) - path = r.limitPath(tenantID) - default: - return int64(0), ErrUnknownRedisOperation - } - - span, ctx := otrace.StartSpanFromContext(ctx, "redis.resource.getOperation.Do") - defer span.Finish() - - // do the redis operation - result, err := r.client.Do(ctx, op, key).Int64() - if err != nil { - return int64(0), err - } - - log.Debugf("Redis Resource %s: '%s' %d", operation, path, result) - return result, nil -} - -// parseArg is used to convert int64 to string -// -// in order to store reliably in redis. -func parseArg(arg int64) string { - return strconv.FormatInt(arg, 10) -} - -// Limited returns true if the limit is enabled. The current limit -// is retrieved from upstream if necessary using the Limiter method. -func (r *Resource) Limited(ctx context.Context, tenantID string) bool { - log := r.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("Resource Limited %s", tenantID) - limited := true - - var limits *tenantLimit - limits, ok := r.tenantLimits[tenantID] - if !ok { - log.Debugf("Create tenantLimits %s", tenantID) - r.tenantLimits[tenantID] = &tenantLimit{ - lastRefreshTime: time.Now(), - } - limits = r.tenantLimits[tenantID] - } - limits.lastRefreshCount++ - elapsed := time.Since(limits.lastRefreshTime) - - // first attempt to get the current limit for the specific tenant - limit, err := r.getLimit(ctx, tenantID) - if err != nil { - // for now if we can't find the limit we set to unlimited? - return false - } - - // we do not need to pull from upstream the local limit will do - log.Debugf("elapsed %v refreshTTL %v lastRefreshCount %d refreshCount %d", - elapsed, r.refreshTTL, limits.lastRefreshCount, r.refreshCount, - ) - if elapsed < r.refreshTTL && limits.lastRefreshCount < r.refreshCount { - log.Debugf("TTL is still ok %s %d", tenantID, limit) - return limit >= 0 - } - - // pull from upstream - log.Infof("Get limit from tenancies service %s %s", r.name, tenantID) - newLimit, err := r.resourceLimiter(ctx, r, tenantID) - if err != nil { - log.Infof("Unable to get upstream limit %s: %v", r.name, err) - // return whatever the old limit was, as we can't get hold of the new limit - return limit >= 0 - } - - // reset the refresh triggers - log.Debugf("Reset the refresh triggers") - limits.lastRefreshCount = 0 - limits.lastRefreshTime = time.Now() - - // check if the new limit is the same as the old limit - if newLimit == limit { - log.Infof("Limit has not changed %s %s", r.name, tenantID) - return limit >= 0 - } - - log.Infof("new limit now %d (from %d)", newLimit, limit) - - // check if the new limit is unlimited - if newLimit == -1 { - limited = false - } - - // set the new limit - log.Debugf("Set Redis %s %d", tenantID, limit) - err = r.setOperation(ctx, opSetLimit, tenantID, newLimit) - if err != nil { - log.Infof("failed to set new limit: %v", err) - // return whatever the old limit was, as we can't set the new limit - return limit >= 0 - } - - return limited -} - -// getLimit gets the limit for a given tenant, attempt first from redis, then upstream. -func (r *Resource) getLimit(ctx context.Context, tenantID string) (int64, error) { - log := r.Log().FromContext(ctx) - defer log.Close() - - // if we have found the limit via redis, then return - log.Debugf("GetLimit From Redis %s", tenantID) - limit, err := r.getOperation(ctx, opGetLimit, tenantID) - if err == nil { - log.Debugf("GetLimit From Redis success %s %d", tenantID, limit) - return limit, nil - } - // we haven't got the redis limit therefore attempt to retrieve from upstream - return r.RefreshLimit(ctx, tenantID) -} - -// RefreshLimit gets the limit for a given tenant from upstream. -func (r *Resource) RefreshLimit(ctx context.Context, tenantID string) (int64, error) { - log := r.Log().FromContext(ctx) - defer log.Close() - - log.Debugf("RefreshLimit From Tenancies %s", tenantID) - limit, err := r.resourceLimiter(ctx, r, tenantID) - if err != nil { - log.Infof("cannot Get From Tenancies %s %s: %v", tenantID, r.name, err) - return int64(0), err - } - - // now we have the upstream value, try and set it in redis - log.Debugf("SetLimit Redis %s %d", tenantID, limit) - err = r.setOperation(ctx, opSetLimit, tenantID, limit) - if err != nil { - // only log error, as we have the upstream limit - log.Infof("failed to set limit: %v", err) - } - - return limit, nil -} - -func (r *Resource) nakedWrite(ctx context.Context, count int64, tenantID string) error { - err := r.setOperation(ctx, opSetCount, tenantID, count) - if err != nil { - return DoError(err, r.countPath(tenantID)) - } - - return nil -} - -// Close closes the resource and client connection to redis -func (r *Resource) Close() error { - if r.client != nil { - return r.client.Close() - } - - return nil -} diff --git a/redis/resource_test.go b/redis/resource_test.go deleted file mode 100644 index ae46e90..0000000 --- a/redis/resource_test.go +++ /dev/null @@ -1,451 +0,0 @@ -package redis - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/datatrails/go-datatrails-common/logger" - "github.com/go-redis/redis/v8" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - ErrMockResource = errors.New("mockerr") -) - -// mockResourceLimit is used to return a mocked resourceLimiter -type mockResourceLimit struct { - limits []int64 - errs []error - nextLimitIndex int -} - -// setup mock limiter -func (mrl *mockResourceLimit) mockResourceLimiter(ctx context.Context, r *Resource, tenantID string) (int64, error) { - - limit := mrl.limits[mrl.nextLimitIndex] - err := mrl.errs[mrl.nextLimitIndex] - - // increment the limit to return if we can - if len(mrl.limits) < mrl.nextLimitIndex { - mrl.nextLimitIndex += 1 - } - - return limit, err -} - -// TestLimited tests the Limited method. -// -// We expect the Limited method to return the upstream limit where possible, -// -// if we do not need to pull from upstream, then we expect Limited to return -// the redis limit. -// -// If an error occurs getting the upstream limit we expect the redis limit to be returned. -// -// If we can't get either the redis or upstream limit, expect unlimited to be returned. -func TestLimited(t *testing.T) { - logger.New("NOOP") - defer logger.OnExit() - - tables := []struct { - subtest string - - // refresh stuff - lastRefreshTime time.Time - refreshTTL time.Duration // in seconds - - lastRefreshCount int64 - refreshCount int64 - - // mock redis - redisGetLimit string - redisGetErr error - redisSetErr error - - // upstream - upstreamLimit int64 - upstreamErr error - - // expected - expected bool - }{ - { - "Successful case getting limit from redis", - - time.Now(), - 10000, // make extremely long so we don't trigger upstream - - 1, - 10, - - // redis limit - "10", - nil, - nil, - - // upstream shouldn't be hit in this test - // but error out if it is. - -1, - ErrMockResource, - - // expected - true, - }, - { - "Successful case getting unlimited from redis", - - time.Now(), - 10000, // make extremely long so we don't trigger upstream - - 1, - 10, - - // redis limit - "[-1]", - nil, - nil, - - // upstream shouldn't be hit in this test - // but error out if it is. - -1, - ErrMockResource, - - // expected - false, - }, - { - "Successful case getting limited from upstream, refreshing TTL", - - time.Now(), - 0, - - 1, - 10, - - // redis limit - "[-1]", // make the redis unlimited - nil, - nil, - - // upstream should be hit so make limited - 10, - nil, - - // expected - true, - }, - { - "Successful case getting limited from upstream, refreshing counter", - - time.Now(), - 100000, - - 9, // counter should hit 10 - 10, - - // redis limit - "[-1]", // make the redis unlimited - nil, - nil, - - // upstream should be hit so make limited - 10, - nil, - - // expected - true, - }, - { - "Successful case getting limited from upstream, failed to get from redis", - - time.Now(), - 100000, - - 1, - 10, - - // redis limit - "[-1]", // make the redis unlimited - ErrMockResource, - nil, - - // upstream should be hit so make limited - 10, - nil, - - // expected - true, - }, - { - "Successful case getting unlimited from upstream, refresh counter", - - time.Now(), - 100000, - - 9, // counter should hit 10 - 10, - - // redis limit - "6", // make the redis limited - nil, - nil, - - // upstream should be hit so make unlimited - -1, - nil, - - // expected - false, - }, - { - "error from setting new limit should revert to old limit", - - time.Now(), - 100000, - - 9, // counter should hit 10 - 10, - - // redis limit - "6", // make the redis limited - nil, - ErrMockResource, - - // upstream should be hit so make unlimited - -1, - nil, - - // expected - true, - }, - { - "error getting limit from redis and upstream", - - time.Now(), - 100000, - - 1, - 10, - - // redis limit - "0", // make the redis limited - ErrMockResource, - nil, - - // upstream should be hit so make limited too - 0, - ErrMockResource, - - // expected - false, - }, - { - "upstream limit is same as redis limit", - - time.Now(), - 100000, - - 9, // counter should hit 10 - 10, - - // redis limit - "801", // make the redis limited - nil, - nil, - - // upstream should be hit so make limited too - 801, - nil, - - // expected - true, - }, - } - - for _, table := range tables { - t.Run(table.subtest, func(t *testing.T) { - - // setup mocking - mClient := new(mockClient) - mClient.On("Do", "GET", mock.Anything).Return(redis.NewCmdResult(table.redisGetLimit, table.redisGetErr)) - mClient.On("Set", mock.Anything, mock.Anything, mock.Anything).Return(redis.NewStatusResult("", table.redisSetErr)) - - // mock the resource limiter - mResourceLimit := mockResourceLimit{ - limits: []int64{table.upstreamLimit}, - errs: []error{table.upstreamErr}, - } - limits := make(map[string]*tenantLimit) - limits["tenantID"] = &tenantLimit{ - lastRefreshCount: table.lastRefreshCount, - lastRefreshTime: table.lastRefreshTime, - } - - // create a Resource that we can use - resource := Resource{ - ClientContext: ClientContext{ - cfg: &clusterConfig{ - namespace: "xxxx", - log: logger.Sugar, - }, - }, - resourceLimiter: mResourceLimit.mockResourceLimiter, - refreshCount: table.refreshCount, - refreshTTL: table.refreshTTL * time.Second, - tenantLimits: limits, - - // add mock client here. - client: mClient, - } - - // the ctx doesn't matter as it is mocked away - actual := resource.Limited(context.TODO(), "tenantID") - - assert.Equal(t, table.expected, actual) - }) - } -} - -// TestLimitedMultipleCalls tests the Limited method after being called multiple times -func TestLimitedMultipleCalls(t *testing.T) { - logger.New("NOOP") - defer logger.OnExit() - - type mockRedis struct { - redisGetLimit string - redisGetErr error - expectRedisSet bool - expectedRedisSet string - redisSetErr error - } - tables := []struct { - name string - - // refresh stuff - lastRefreshTime time.Time - refreshTTL time.Duration // in seconds - - lastRefreshCount int64 - refreshCount int64 - - // redis - mockRedis []mockRedis - - // upstream - upstreamLimits []int64 - upstreamErrs []error - }{ - { - name: "Successful case getting limit from redis", - - lastRefreshTime: time.Now(), - refreshTTL: 10000, // make extremely long so we don't trigger upstream - - lastRefreshCount: 0, // start on 0 - refreshCount: 2, // refresh every 2 calls - - // redis limit - mockRedis: []mockRedis{ - { - redisGetLimit: "1", // start by saying the limit is 1 - redisGetErr: nil, - - expectRedisSet: false, // using redis value, no new value set - }, - { - redisGetLimit: "1", // limit is still 1 - redisGetErr: nil, - - expectRedisSet: true, // we have ticked over the refresh so expect upstream - expectedRedisSet: "2", - redisSetErr: nil, - }, - { - redisGetLimit: "2", // limit is now 2 - redisGetErr: nil, - - expectRedisSet: false, // using redis value, no new value set - }, - { - redisGetLimit: "2", // limit is still 2 - redisGetErr: nil, - - expectRedisSet: true, // we have ticked over the refresh so expect upstream - expectedRedisSet: "3", - redisSetErr: nil, - }, - { - redisGetLimit: "3", // limit is now 3 - redisGetErr: nil, - - expectRedisSet: false, // using redis value, no new value set - }, - }, - - // upstream shouldn't be hit in this test - // but error out if it is. - upstreamLimits: []int64{2, 3}, - upstreamErrs: []error{nil, nil}, - }, - } - - for _, table := range tables { - t.Run(table.name, func(t *testing.T) { - - // setup mocking - mClient := new(mockClient) - - for _, mockRedis := range table.mockRedis { - mClient.On("Do", "GET", mock.Anything).Return(redis.NewCmdResult(mockRedis.redisGetLimit, mockRedis.redisGetErr)).Once() - - if mockRedis.expectRedisSet { - // expect to set the correct value here - mClient.On("Set", mock.Anything, mockRedis.expectedRedisSet, mock.Anything).Return(redis.NewStatusResult("", mockRedis.redisSetErr)).Once() - } - } - - // mock the resource limiter - mResourceLimit := mockResourceLimit{ - limits: table.upstreamLimits, - errs: table.upstreamErrs, - } - limits := make(map[string]*tenantLimit) - limits["tenantID"] = &tenantLimit{ - lastRefreshCount: table.lastRefreshCount, - lastRefreshTime: table.lastRefreshTime, - } - - // create a Resource that we can use - resource := Resource{ - ClientContext: ClientContext{ - cfg: &clusterConfig{ - namespace: "xxxx", - log: logger.Sugar, - }, - }, - resourceLimiter: mResourceLimit.mockResourceLimiter, - refreshCount: table.refreshCount, - refreshTTL: table.refreshTTL * time.Second, - tenantLimits: limits, - - // add mock client here. - client: mClient, - } - - // check the limit is correctly retrieved - for range table.mockRedis { - - // the ctx doesn't matter as it is mocked away - ctx := context.Background() - - // call limited, the assertion is in the mocks above - resource.Limited(ctx, "tenantID") - - } - }) - } -} diff --git a/redis/size_resource.go b/redis/size_resource.go deleted file mode 100644 index 7880845..0000000 --- a/redis/size_resource.go +++ /dev/null @@ -1,57 +0,0 @@ -package redis - -// Handles creating a size limit. -// -// The underlying size limit is stored in REDIS. The size limit can be retrieved using 'Available()', -// which first attempts to get the limit via REDIS, then upstream if REDIS is unavailable. - -import ( - "context" - "fmt" -) - -type SizeResource struct { - *Resource -} - -// Available gets current size limit. -func (sr *SizeResource) Available(ctx context.Context, tenantID string) (int64, error) { - return sr.getOperation(ctx, opGetLimit, tenantID) -} - -// If limit is less than zero then the limit is disabled and all methods are noops. -func NewSizeResource( - cfg RedisConfig, - name string, - opts ...ResourceOption, -) (*SizeResource, error) { - - log := cfg.Log() - - log.Debugf("%s SizeResource", name) - - client, err := NewRedisClient(cfg) - if err != nil { - return nil, fmt.Errorf("failed to create redis client for new size resource: %w", err) - } - - r := &SizeResource{ - Resource: &Resource{ - ClientContext: ClientContext{ - cfg: cfg, - name: name, - }, - resourceLimiter: defaultResourceLimiter, - refreshTTL: defaultTTL, - refreshCount: defaultRefreshCount, - tenantLimits: make(map[string]*tenantLimit), - client: client, - }, - } - - for _, opt := range opts { - opt(r.Resource) - } - - return r, nil -} diff --git a/redis/test_helpers.go b/redis/test_helpers.go deleted file mode 100644 index 75c7be2..0000000 --- a/redis/test_helpers.go +++ /dev/null @@ -1,41 +0,0 @@ -package redis - -import "github.com/go-redis/redis/v8" - -func NewResourceWithMockedRedis( - name string, - log Logger, - opt CountResourceOption, - opts ...ResourceOption, -) (*CountResource, *mockClient) { - mClient := &mockClient{} - r := &CountResource{ - Resource: &Resource{ - ClientContext: ClientContext{ - cfg: &clusterConfig{ - Size: 0, - namespace: "something", - clusterOptions: redis.ClusterOptions{}, - options: redis.Options{}, - log: log, - }, - name: name, - }, - resourceLimiter: defaultResourceLimiter, - refreshTTL: defaultTTL, - refreshCount: defaultRefreshCount, - tenantLimits: make(map[string]*tenantLimit), - client: mClient, // Don't use the real thing. - }, - resourceCounter: defaultResourceCounter, - } - - // set the resource counter - opt(r) - - for _, opt := range opts { - opt(r.Resource) - } - - return r, mClient -} diff --git a/tracing/environment.go b/tracing/environment.go new file mode 100644 index 0000000..415d7c6 --- /dev/null +++ b/tracing/environment.go @@ -0,0 +1,33 @@ +package tracing + +import ( + "os" + "strconv" + + "github.com/datatrails/go-datatrails-common/logger" +) + +const ( + commaSeparator = "," +) + +func getOrFatal(key string) string { + value, ok := os.LookupEnv(key) + if !ok { + logger.Sugar.Panicf("required environment variable is not defined: %s", key) + } + return value +} + +func getTruthyOrFatal(key string) bool { + value, ok := os.LookupEnv(key) + if !ok { + logger.Sugar.Panicf("environment variable %s not found", key) + } + // t,true,True,1 are all examples of 'truthy' values understood by ParseBool + b, err := strconv.ParseBool(value) + if err != nil { + logger.Sugar.Panicf("environment variable %s not valid truthy value: %v", key, err) + } + return b +} diff --git a/tracing/tracing.go b/tracing/tracing.go index 4fd47ce..96e12dc 100644 --- a/tracing/tracing.go +++ b/tracing/tracing.go @@ -14,8 +14,6 @@ import ( otnethttp "github.com/opentracing-contrib/go-stdlib/nethttp" opentracing "github.com/opentracing/opentracing-go" - "github.com/datatrails/go-datatrails-common/environment" - zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" zipkin "github.com/openzipkin/zipkin-go" zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" @@ -120,11 +118,11 @@ func trimPodName(p string) string { } func NewTracer(log Logger, portName string) io.Closer { - instanceName, _, _ := strings.Cut(environment.GetOrFatal("POD_NAME"), " ") - nameSpace := environment.GetOrFatal("POD_NAMESPACE") - containerName := environment.GetOrFatal("CONTAINER_NAME") + instanceName, _, _ := strings.Cut(getOrFatal("POD_NAME"), " ") + nameSpace := getOrFatal("POD_NAMESPACE") + containerName := getOrFatal("CONTAINER_NAME") podName := strings.Join([]string{trimPodName(instanceName), nameSpace, containerName}, ".") - listenStr := fmt.Sprintf("localhost:%s", environment.GetOrFatal(portName)) + listenStr := fmt.Sprintf("localhost:%s", getOrFatal(portName)) return NewFromEnv(log, strings.TrimSpace(podName), listenStr, "ZIPKIN_ENDPOINT", "DISABLE_ZIPKIN") } @@ -135,7 +133,7 @@ func NewTracer(log Logger, portName string) io.Closer { func NewFromEnv(log Logger, service string, host string, endpointVar, disableVar string) io.Closer { ze, ok := os.LookupEnv(endpointVar) if !ok { - if disabled := environment.GetTruthyOrFatal(disableVar); !disabled { + if disabled := getTruthyOrFatal(disableVar); !disabled { log.Panicf( "'%s' has not been provided and is not disabled by '%s'", endpointVar, disableVar) @@ -145,7 +143,7 @@ func NewFromEnv(log Logger, service string, host string, endpointVar, disableVar } // zipkin conf is available, disable it if disableVar is truthy - if disabled := environment.GetTruthyOrFatal(disableVar); disabled { + if disabled := getTruthyOrFatal(disableVar); disabled { log.Infof("'%s' set, zipkin disabled", disableVar) return nil }