mirror of https://github.com/dapr/kit.git
Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
|
8b780b4d81 | |
|
9d4f384c57 | |
|
7c4cedad37 | |
|
7409957e9e | |
|
34f8820d2a | |
|
598b032bce | |
|
d7d50a1e1b | |
|
baea626399 | |
|
bc7dc566c4 | |
|
98fe567235 | |
|
e3d4a8f1b4 |
|
@ -24,7 +24,7 @@ jobs:
|
|||
GOOS: ${{ matrix.target_os }}
|
||||
GOARCH: ${{ matrix.target_arch }}
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GOLANGCI_LINT_VER: v1.61.0
|
||||
GOLANGCI_LINT_VER: v1.64.8
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
|
@ -54,9 +54,6 @@ jobs:
|
|||
with:
|
||||
version: ${{ env.GOLANGCI_LINT_VER }}
|
||||
skip-cache: true
|
||||
# TODO: @joshvanl remove once all new linter errors have been
|
||||
# addressed.
|
||||
only-new-issues: true
|
||||
- name: Run make go.mod check-diff
|
||||
if: matrix.target_arch != 'arm'
|
||||
run: make go.mod check-diff
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
.idea
|
||||
.vscode
|
||||
.vs
|
||||
vendor
|
195
.golangci.yml
195
.golangci.yml
|
@ -4,7 +4,7 @@ run:
|
|||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 10m
|
||||
timeout: 15m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
@ -15,29 +15,34 @@ run:
|
|||
# list of build tags, all linters use it. Default is empty list.
|
||||
build-tags:
|
||||
- unit
|
||||
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs:
|
||||
- ^pkg.*client.*clientset.*versioned.*
|
||||
- ^pkg.*client.*informers.*externalversions.*
|
||||
- ^pkg.*proto.*
|
||||
- allcomponents
|
||||
- subtlecrypto
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
# skip-files:
|
||||
# - ".*\\.my\\.go$"
|
||||
# - lib/bad.go
|
||||
|
||||
issues:
|
||||
# which dirs to skip: they won't be analyzed;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but next dirs are always skipped independently
|
||||
# from this option's value:
|
||||
# third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
exclude-dirs:
|
||||
- ^pkg.*client.*clientset.*versioned.*
|
||||
- ^pkg.*client.*informers.*externalversions.*
|
||||
- ^pkg.*proto.*
|
||||
- pkg/proto
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number"
|
||||
format: tab
|
||||
formats:
|
||||
- format: tab
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
|
@ -57,23 +62,19 @@ linters-settings:
|
|||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
ignore: fmt:.*,io/ioutil:^Read.*
|
||||
exclude-functions:
|
||||
- fmt:.*
|
||||
- io/ioutil:^Read.*
|
||||
|
||||
# path to a file containing a list of functions to exclude from checking
|
||||
# see https://github.com/kisielk/errcheck#excluding-functions for details
|
||||
exclude:
|
||||
# exclude:
|
||||
|
||||
funlen:
|
||||
lines: 60
|
||||
statements: 40
|
||||
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
|
||||
# settings per analyzer
|
||||
settings:
|
||||
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||
|
@ -86,13 +87,12 @@ linters-settings:
|
|||
# enable or disable analyzers by name
|
||||
enable:
|
||||
- atomicalign
|
||||
enable-all: false
|
||||
disable:
|
||||
- shadow
|
||||
enable-all: false
|
||||
disable-all: false
|
||||
golint:
|
||||
revive:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
confidence: 0.8
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
|
@ -106,9 +106,6 @@ linters-settings:
|
|||
gocognit:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 10
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
|
@ -121,55 +118,60 @@ linters-settings:
|
|||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: "github.com/Sirupsen/logrus"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "github.com/agrea/ptr"
|
||||
desc: "must use github.com/dapr/kit/ptr"
|
||||
- pkg: "go.uber.org/atomic"
|
||||
desc: "must use sync/atomic"
|
||||
- pkg: "golang.org/x/net/context"
|
||||
desc: "must use context"
|
||||
- pkg: "github.com/pkg/errors"
|
||||
desc: "must use standard library (errors package and/or fmt.Errorf)"
|
||||
- pkg: "github.com/go-chi/chi$"
|
||||
desc: "must use github.com/go-chi/chi/v5"
|
||||
- pkg: "github.com/cenkalti/backoff$"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/cenkalti/backoff/v2"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/cenkalti/backoff/v3"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/benbjohnson/clock"
|
||||
desc: "must use k8s.io/utils/clock"
|
||||
- pkg: "github.com/ghodss/yaml"
|
||||
desc: "must use sigs.k8s.io/yaml"
|
||||
- pkg: "gopkg.in/yaml.v2"
|
||||
desc: "must use gopkg.in/yaml.v3"
|
||||
- pkg: "github.com/golang-jwt/jwt"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v2"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v3"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v4"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/gogo/status"
|
||||
desc: "must use google.golang.org/grpc/status"
|
||||
- pkg: "github.com/gogo/protobuf"
|
||||
desc: "must use google.golang.org/protobuf"
|
||||
- pkg: "github.com/lestrrat-go/jwx/jwa"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/lestrrat-go/jwx/jwt"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/labstack/gommon/log"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "github.com/gobuffalo/logger"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "github.com/Sirupsen/logrus"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "github.com/agrea/ptr"
|
||||
desc: "must use github.com/dapr/kit/ptr"
|
||||
- pkg: "go.uber.org/atomic"
|
||||
desc: "must use sync/atomic"
|
||||
- pkg: "golang.org/x/net/context"
|
||||
desc: "must use context"
|
||||
- pkg: "github.com/pkg/errors"
|
||||
desc: "must use standard library (errors package and/or fmt.Errorf)"
|
||||
- pkg: "github.com/go-chi/chi$"
|
||||
desc: "must use github.com/go-chi/chi/v5"
|
||||
- pkg: "github.com/cenkalti/backoff$"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/cenkalti/backoff/v2"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/cenkalti/backoff/v3"
|
||||
desc: "must use github.com/cenkalti/backoff/v4"
|
||||
- pkg: "github.com/benbjohnson/clock"
|
||||
desc: "must use k8s.io/utils/clock"
|
||||
- pkg: "github.com/ghodss/yaml"
|
||||
desc: "must use sigs.k8s.io/yaml"
|
||||
- pkg: "gopkg.in/yaml.v2"
|
||||
desc: "must use gopkg.in/yaml.v3"
|
||||
- pkg: "github.com/golang-jwt/jwt"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v2"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v3"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/golang-jwt/jwt/v4"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
# pkg: Commonly auto-completed by gopls
|
||||
- pkg: "github.com/gogo/status"
|
||||
desc: "must use google.golang.org/grpc/status"
|
||||
- pkg: "github.com/gogo/protobuf"
|
||||
desc: "must use google.golang.org/protobuf"
|
||||
- pkg: "github.com/lestrrat-go/jwx/jwa"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/lestrrat-go/jwx/jwt"
|
||||
desc: "must use github.com/lestrrat-go/jwx/v2"
|
||||
- pkg: "github.com/labstack/gommon/log"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "github.com/gobuffalo/logger"
|
||||
desc: "must use github.com/dapr/kit/logger"
|
||||
- pkg: "k8s.io/utils/pointer"
|
||||
desc: "must use github.com/dapr/kit/ptr"
|
||||
- pkg: "k8s.io/utils/ptr"
|
||||
desc: "must use github.com/dapr/kit/ptr"
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: default
|
||||
# locale: default
|
||||
ignore-words:
|
||||
- someword
|
||||
lll:
|
||||
|
@ -178,17 +180,9 @@ linters-settings:
|
|||
line-length: 120
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
nolintlint:
|
||||
allow-unused: true
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
@ -203,7 +197,6 @@ linters-settings:
|
|||
# See https://go-critic.github.io/overview#checks-overview
|
||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||
# By default list of stable checks is used.
|
||||
enabled-checks:
|
||||
|
||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||
disabled-checks:
|
||||
|
@ -251,62 +244,51 @@ linters-settings:
|
|||
allow-assign-and-call: true
|
||||
# Allow multiline assignments to be cuddled. Default is true.
|
||||
allow-multiline-assign: true
|
||||
# Allow case blocks to end with a whitespace.
|
||||
allow-case-traling-whitespace: true
|
||||
# Allow declarations (var) to be cuddled.
|
||||
allow-cuddle-declarations: false
|
||||
# If the number of lines in a case block is equal to or lager than this number,
|
||||
# the case *must* end white a newline.
|
||||
# https://github.com/bombsimon/wsl/blob/master/doc/configuration.md#force-case-trailing-whitespace
|
||||
# Default: 0
|
||||
force-case-trailing-whitespace: 1
|
||||
|
||||
linters:
|
||||
fast: false
|
||||
enable-all: true
|
||||
disable:
|
||||
# TODO Enforce the below linters later
|
||||
- musttag
|
||||
- dupl
|
||||
- nonamedreturns
|
||||
- errcheck
|
||||
- funlen
|
||||
- goconst
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- nosnakecase
|
||||
- varcheck
|
||||
- structcheck
|
||||
- deadcode
|
||||
- godox
|
||||
- interfacer
|
||||
- lll
|
||||
- maligned
|
||||
- scopelint
|
||||
- unparam
|
||||
- wsl
|
||||
- gomnd
|
||||
- testpackage
|
||||
- nestif
|
||||
- nlreturn
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- noctx
|
||||
- gci
|
||||
- golint
|
||||
- tparallel
|
||||
- paralleltest
|
||||
- wrapcheck
|
||||
- tagliatelle
|
||||
- ireturn
|
||||
- exhaustive
|
||||
- exhaustivestruct
|
||||
- exhaustruct
|
||||
- errchkjson
|
||||
- contextcheck
|
||||
- gomoddirectives
|
||||
- godot
|
||||
- cyclop
|
||||
- varnamelen
|
||||
- gosec
|
||||
- tagalign
|
||||
- errorlint
|
||||
- forcetypeassert
|
||||
- ifshort
|
||||
- maintidx
|
||||
- nilnil
|
||||
- predeclared
|
||||
|
@ -315,4 +297,13 @@ linters:
|
|||
- wastedassign
|
||||
- containedctx
|
||||
- gosimple
|
||||
- forbidigo
|
||||
- nonamedreturns
|
||||
- asasalint
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- inamedparam
|
||||
- tagalign
|
||||
- mnd
|
||||
- canonicalheader
|
||||
- err113
|
||||
- fatcontext
|
||||
|
|
7
Makefile
7
Makefile
|
@ -78,6 +78,13 @@ test-race:
|
|||
lint:
|
||||
$(GOLANGCI_LINT) run --timeout=20m
|
||||
|
||||
################################################################################
|
||||
# Target: lint-fix #
|
||||
################################################################################
|
||||
.PHONY: lint-fix
|
||||
lint-fix:
|
||||
$(GOLANGCI_LINT) run --timeout=20m --fix
|
||||
|
||||
################################################################################
|
||||
# Target: go.mod #
|
||||
################################################################################
|
||||
|
|
|
@ -32,7 +32,7 @@ func TestByteSlicePool(t *testing.T) {
|
|||
assert.Equal(t, &bs, &bs2)
|
||||
assert.Equal(t, minCap, cap(bs2))
|
||||
|
||||
for i := 0; i < minCap; i++ {
|
||||
for range minCap {
|
||||
bs2 = append(bs2, 0)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,7 @@ import (
|
|||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrManagerAlreadyClosed = errors.New("runner manager already closed")
|
||||
)
|
||||
var ErrManagerAlreadyClosed = errors.New("runner manager already closed")
|
||||
|
||||
// RunnerCloserManager is a RunnerManager that also implements Closing of the
|
||||
// added closers once the main runners are done.
|
||||
|
|
|
@ -38,7 +38,7 @@ func (m mockCloser) Close() error {
|
|||
|
||||
func Test_RunnerClosterManager(t *testing.T) {
|
||||
t.Run("runner with no tasks or closers should return nil", func(t *testing.T) {
|
||||
require.NoError(t, NewRunnerCloserManager(log, nil).Run(context.Background()))
|
||||
require.NoError(t, NewRunnerCloserManager(log, nil).Run(t.Context()))
|
||||
})
|
||||
|
||||
t.Run("runner with a task that completes should return nil", func(t *testing.T) {
|
||||
|
@ -46,7 +46,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
require.NoError(t, NewRunnerCloserManager(log, nil, func(context.Context) error {
|
||||
i.Add(1)
|
||||
return nil
|
||||
}).Run(context.Background()))
|
||||
}).Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i.Load())
|
||||
})
|
||||
|
||||
|
@ -60,7 +60,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
i.Add(1)
|
||||
return nil
|
||||
}))
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, int32(2), i.Load())
|
||||
})
|
||||
|
||||
|
@ -98,7 +98,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
}),
|
||||
))
|
||||
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, int32(7), i.Load())
|
||||
})
|
||||
|
||||
|
@ -125,7 +125,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
},
|
||||
))
|
||||
|
||||
require.EqualError(t, mngr.Run(context.Background()), "error")
|
||||
require.EqualError(t, mngr.Run(t.Context()), "error")
|
||||
assert.Equal(t, int32(4), i.Load())
|
||||
})
|
||||
|
||||
|
@ -152,7 +152,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
},
|
||||
))
|
||||
|
||||
require.EqualError(t, mngr.Run(context.Background()), "error")
|
||||
require.EqualError(t, mngr.Run(t.Context()), "error")
|
||||
assert.Equal(t, int32(4), i.Load())
|
||||
})
|
||||
|
||||
|
@ -187,7 +187,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
}),
|
||||
))
|
||||
|
||||
err := mngr.Run(context.Background())
|
||||
err := mngr.Run(t.Context())
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "error\nerror\nerror\nclosererror\nclosererror\nclosererror") //nolint:dupword
|
||||
assert.Equal(t, int32(6), i.Load())
|
||||
|
@ -224,7 +224,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
}),
|
||||
))
|
||||
|
||||
err := mngr.Run(context.Background())
|
||||
err := mngr.Run(t.Context())
|
||||
require.Error(t, err)
|
||||
assert.ElementsMatch(t,
|
||||
[]string{"error1", "error2", "error3", "closererror1", "closererror2", "closererror3"},
|
||||
|
@ -265,7 +265,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
},
|
||||
))
|
||||
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, int32(5), i.Load())
|
||||
})
|
||||
|
||||
|
@ -325,7 +325,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
},
|
||||
))
|
||||
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, int32(6), i.Load())
|
||||
})
|
||||
|
||||
|
@ -377,7 +377,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
},
|
||||
))
|
||||
|
||||
err := mngr.Run(context.Background())
|
||||
err := mngr.Run(t.Context())
|
||||
require.Error(t, err)
|
||||
assert.ElementsMatch(t,
|
||||
[]string{"error1", "error2", "error3", "closererror1", "closererror2"},
|
||||
|
@ -392,9 +392,9 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
i.Add(1)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i.Load())
|
||||
require.EqualError(t, m.Run(context.Background()), "runner manager already started")
|
||||
require.EqualError(t, m.Run(t.Context()), "runner manager already started")
|
||||
assert.Equal(t, int32(1), i.Load())
|
||||
})
|
||||
|
||||
|
@ -410,11 +410,11 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
return nil
|
||||
}))
|
||||
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(2), i.Load())
|
||||
require.NoError(t, m.Close())
|
||||
require.NoError(t, m.Close())
|
||||
require.EqualError(t, m.Run(context.Background()), "runner manager already started")
|
||||
require.EqualError(t, m.Run(t.Context()), "runner manager already started")
|
||||
assert.Equal(t, int32(2), i.Load())
|
||||
})
|
||||
|
||||
|
@ -424,7 +424,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
i.Add(1)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i.Load())
|
||||
err := m.Add(func(context.Context) error {
|
||||
i.Add(1)
|
||||
|
@ -441,7 +441,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
i.Add(1)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i.Load())
|
||||
require.NoError(t, m.Close())
|
||||
err := m.AddCloser(func(context.Context) error {
|
||||
|
@ -486,7 +486,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -515,7 +515,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
clock := clocktesting.NewFakeClock(time.Now())
|
||||
mngr.clock = clock
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
fatalCalled := make(chan struct{})
|
||||
|
@ -537,7 +537,7 @@ func Test_RunnerClosterManager(t *testing.T) {
|
|||
}
|
||||
})
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
|
@ -571,7 +571,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -629,7 +629,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -714,7 +714,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -776,7 +776,7 @@ func TestClose(t *testing.T) {
|
|||
assert.Len(t, mngr.closers, 1)
|
||||
|
||||
returnClose := make(chan struct{})
|
||||
for n := 0; n < 4; n++ {
|
||||
for range 4 {
|
||||
require.NoError(t, mngr.AddCloser(func() {
|
||||
i.Add(1)
|
||||
<-returnClose
|
||||
|
@ -787,7 +787,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -850,7 +850,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
var err error
|
||||
|
@ -879,7 +879,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
require.NoError(t, mngr.Close())
|
||||
require.NoError(t, mngr.Close())
|
||||
assert.Equal(t, mngr.Run(context.Background()), errors.New("runner manager already started"))
|
||||
assert.Equal(t, mngr.Run(t.Context()), errors.New("runner manager already started"))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -918,7 +918,7 @@ func TestAddCloser(t *testing.T) {
|
|||
return nil
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(ctx)
|
||||
|
@ -931,7 +931,7 @@ func TestAddCloser(t *testing.T) {
|
|||
t.Run("should error if closing", func(t *testing.T) {
|
||||
mngr := NewRunnerCloserManager(log, nil)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
closerCh := make(chan struct{})
|
||||
require.NoError(t, mngr.AddCloser(func() {
|
||||
cancel()
|
||||
|
@ -940,7 +940,7 @@ func TestAddCloser(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- mngr.Run(context.Background())
|
||||
errCh <- mngr.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -973,7 +973,7 @@ func TestAddCloser(t *testing.T) {
|
|||
|
||||
t.Run("should error if manager already returned", func(t *testing.T) {
|
||||
mngr := NewRunnerCloserManager(log, nil)
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, mngr.AddCloser(nil), errors.New("runner manager already closed"))
|
||||
})
|
||||
}
|
||||
|
@ -999,7 +999,7 @@ func TestWaitUntilShutdown(t *testing.T) {
|
|||
<-returnClose
|
||||
}))
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
|
|
|
@ -57,13 +57,13 @@ func TestAtomicInt32_New_Get_Delete(t *testing.T) {
|
|||
for _, key := range keys {
|
||||
go func(k string) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
m.GetOrCreate(k, 0).Add(1)
|
||||
}
|
||||
}(key)
|
||||
go func(k string) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < iterations; i++ {
|
||||
for range iterations {
|
||||
m.GetOrCreate(k, 0).Add(-1)
|
||||
}
|
||||
}(key)
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestNewMutex_Add_Delete(t *testing.T) {
|
|||
wg.Add(numGoroutines)
|
||||
|
||||
// Concurrently lock and unlock for each key
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for range numGoroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mm.Lock("key1")
|
||||
|
@ -75,7 +75,7 @@ func TestNewMutex_Add_Delete(t *testing.T) {
|
|||
wg.Add(numGoroutines * 2)
|
||||
|
||||
// Concurrently RLock and RUnlock for each key
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for range numGoroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mm.RLock("key1")
|
||||
|
@ -87,7 +87,7 @@ func TestNewMutex_Add_Delete(t *testing.T) {
|
|||
assert.Equal(ct, int64(10), counter.Load())
|
||||
}, 5*time.Second, 10*time.Millisecond)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
for range numGoroutines {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mm.RUnlock("key1")
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctesting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dapr/kit/concurrency"
|
||||
"github.com/dapr/kit/concurrency/ctesting/internal"
|
||||
)
|
||||
|
||||
type RunnerFn func(context.Context, assert.TestingT)
|
||||
|
||||
// Assert runs the provided test functions in parallel and asserts that they
|
||||
// all pass.
|
||||
func Assert(t *testing.T, runners ...RunnerFn) {
|
||||
t.Helper()
|
||||
|
||||
if len(runners) == 0 {
|
||||
require.Fail(t, "at least one runner function is required")
|
||||
}
|
||||
|
||||
tt := internal.Assert(t)
|
||||
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
doneCh := make(chan struct{}, len(runners))
|
||||
for _, runner := range runners {
|
||||
go func(rfn RunnerFn) {
|
||||
rfn(ctx, tt)
|
||||
if errs := tt.Errors(); len(errs) > 0 {
|
||||
cancel(errors.Join(errs...))
|
||||
}
|
||||
doneCh <- struct{}{}
|
||||
}(runner)
|
||||
}
|
||||
|
||||
for range runners {
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-t.Context().Done():
|
||||
require.FailNow(t, "test context was cancelled before all runners completed")
|
||||
}
|
||||
}
|
||||
|
||||
for _, err := range tt.Errors() {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertCleanup runs the provided test functions in parallel and asserts that they
|
||||
// all pass, only after Cleanup,.
|
||||
func AssertCleanup(t *testing.T, runners ...concurrency.Runner) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithCancelCause(t.Context())
|
||||
|
||||
errCh := make(chan error, len(runners))
|
||||
for _, runner := range runners {
|
||||
go func(rfn concurrency.Runner) {
|
||||
errCh <- rfn(ctx)
|
||||
}(runner)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
cancel(nil)
|
||||
for range runners {
|
||||
select {
|
||||
case err := <-errCh:
|
||||
require.NoError(t, err)
|
||||
case <-time.After(10 * time.Second):
|
||||
assert.Fail(t, "timeout waiting for runner to stop")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
assert.TestingT
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
type assertT struct {
|
||||
t *testing.T
|
||||
lock sync.Mutex
|
||||
errs []error
|
||||
}
|
||||
|
||||
func Assert(t *testing.T) Interface {
|
||||
return &assertT{t: t}
|
||||
}
|
||||
|
||||
func (a *assertT) Errorf(format string, args ...any) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.errs = append(a.errs, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
func (a *assertT) Errors() []error {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
return a.errs
|
||||
}
|
|
@ -50,17 +50,17 @@ func New(opts Options) *Dir {
|
|||
func (d *Dir) Write(files map[string][]byte) error {
|
||||
newDir := filepath.Join(d.base, fmt.Sprintf("%d-%s", time.Now().UTC().UnixNano(), d.targetDir))
|
||||
|
||||
if err := os.MkdirAll(d.base, os.ModePerm); err != nil {
|
||||
if err := os.MkdirAll(d.base, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(newDir, os.ModePerm); err != nil {
|
||||
if err := os.MkdirAll(newDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for file, b := range files {
|
||||
path := filepath.Join(newDir, file)
|
||||
if err := os.WriteFile(path, b, os.ModePerm); err != nil {
|
||||
if err := os.WriteFile(path, b, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
d.log.Infof("Written file %s", file)
|
||||
|
|
|
@ -29,14 +29,14 @@ func Test_Context(t *testing.T) {
|
|||
}{
|
||||
"Successful Lock": {
|
||||
action: func(l *Context) error {
|
||||
return l.Lock(context.Background())
|
||||
return l.Lock(t.Context())
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
"Lock with Context Timeout": {
|
||||
action: func(l *Context) error {
|
||||
l.Lock(context.Background())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
|
||||
l.Lock(t.Context())
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond*50)
|
||||
defer cancel()
|
||||
return l.Lock(ctx)
|
||||
},
|
||||
|
@ -44,14 +44,14 @@ func Test_Context(t *testing.T) {
|
|||
},
|
||||
"Successful RLock": {
|
||||
action: func(l *Context) error {
|
||||
return l.RLock(context.Background())
|
||||
return l.RLock(t.Context())
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
"RLock with Context Timeout": {
|
||||
action: func(l *Context) error {
|
||||
l.Lock(context.Background())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
|
||||
l.Lock(t.Context())
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond*50)
|
||||
defer cancel()
|
||||
return l.RLock(ctx)
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
go l.Run(ctx)
|
||||
|
@ -71,7 +71,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
go l.Run(ctx)
|
||||
|
@ -97,7 +97,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
go l.Run(ctx)
|
||||
|
@ -127,7 +127,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
|
||||
go l.Run(ctx)
|
||||
|
@ -138,7 +138,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
assert.Fail(t, "expected close")
|
||||
}
|
||||
|
||||
_, _, err := l.RLock(context.Background())
|
||||
_, _, err := l.RLock(t.Context())
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
|
@ -147,7 +147,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
|
||||
l.Run(ctx)
|
||||
|
@ -163,7 +163,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
go l.Run(ctx)
|
||||
|
@ -179,6 +179,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
t.Cleanup(c1)
|
||||
close(gotRLock)
|
||||
}()
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, <-errCh)
|
||||
})
|
||||
|
@ -190,6 +191,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
}
|
||||
|
||||
lcancel()
|
||||
<-gotRLock
|
||||
})
|
||||
|
||||
t.Run("lock blocks until outter unlocks", func(t *testing.T) {
|
||||
|
@ -197,7 +199,7 @@ func Test_OuterCancel(t *testing.T) {
|
|||
|
||||
l := NewOuterCancel(terr, time.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
go l.Run(ctx)
|
||||
|
|
|
@ -60,17 +60,12 @@ func (r *RunnerManager) Run(ctx context.Context) error {
|
|||
return ErrManagerAlreadyStarted
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
defer cancel(nil)
|
||||
|
||||
errCh := make(chan error)
|
||||
for _, runner := range r.runners {
|
||||
go func(runner Runner) {
|
||||
// Since the task returned, we need to cancel all other tasks.
|
||||
// This is a noop if the parent context is already cancelled, or another
|
||||
// task returned before this one.
|
||||
defer cancel()
|
||||
|
||||
// Ignore context cancelled errors since errors from a runner manager
|
||||
// will likely determine the exit code of the program.
|
||||
// Context cancelled errors are also not really useful to the user in
|
||||
|
@ -78,15 +73,20 @@ func (r *RunnerManager) Run(ctx context.Context) error {
|
|||
rErr := runner(ctx)
|
||||
if rErr != nil && !errors.Is(rErr, context.Canceled) {
|
||||
errCh <- rErr
|
||||
// Since the task returned, we need to cancel all other tasks.
|
||||
// This is a noop if the parent context is already cancelled, or another
|
||||
// task returned before this one.
|
||||
cancel(rErr)
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
cancel(nil)
|
||||
}(runner)
|
||||
}
|
||||
|
||||
// Collect all errors
|
||||
errObjs := make([]error, 0)
|
||||
for i := 0; i < len(r.runners); i++ {
|
||||
for range len(r.runners) {
|
||||
err := <-errCh
|
||||
if err != nil {
|
||||
errObjs = append(errObjs, err)
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
func Test_RunnerManager(t *testing.T) {
|
||||
t.Run("runner with no tasks should return nil", func(t *testing.T) {
|
||||
require.NoError(t, NewRunnerManager().Run(context.Background()))
|
||||
require.NoError(t, NewRunnerManager().Run(t.Context()))
|
||||
})
|
||||
|
||||
t.Run("runner with a task that completes should return nil", func(t *testing.T) {
|
||||
|
@ -35,7 +35,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
require.NoError(t, NewRunnerManager(func(ctx context.Context) error {
|
||||
atomic.AddInt32(&i, 1)
|
||||
return nil
|
||||
}).Run(context.Background()))
|
||||
}).Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i)
|
||||
})
|
||||
|
||||
|
@ -54,7 +54,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return nil
|
||||
},
|
||||
).Run(context.Background()))
|
||||
).Run(t.Context()))
|
||||
assert.Equal(t, int32(3), i)
|
||||
})
|
||||
|
||||
|
@ -73,7 +73,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return nil
|
||||
},
|
||||
).Run(context.Background()), "error")
|
||||
).Run(t.Context()), "error")
|
||||
assert.Equal(t, int32(3), i)
|
||||
})
|
||||
|
||||
|
@ -92,7 +92,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return errors.New("error")
|
||||
},
|
||||
).Run(context.Background())
|
||||
).Run(t.Context())
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "error\nerror\nerror") //nolint:dupword
|
||||
assert.Equal(t, int32(3), i)
|
||||
|
@ -113,7 +113,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return errors.New("error3")
|
||||
},
|
||||
).Run(context.Background())
|
||||
).Run(t.Context())
|
||||
require.Error(t, err)
|
||||
assert.ElementsMatch(t, []string{"error1", "error2", "error3"}, strings.Split(err.Error(), "\n"))
|
||||
assert.Equal(t, int32(3), i)
|
||||
|
@ -139,7 +139,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
return nil
|
||||
},
|
||||
))
|
||||
require.NoError(t, mngr.Run(context.Background()))
|
||||
require.NoError(t, mngr.Run(t.Context()))
|
||||
assert.Equal(t, int32(3), i)
|
||||
})
|
||||
|
||||
|
@ -168,7 +168,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
).Run(context.Background()))
|
||||
).Run(t.Context()))
|
||||
assert.Equal(t, int32(3), i)
|
||||
})
|
||||
|
||||
|
@ -197,7 +197,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return errors.New("error3")
|
||||
},
|
||||
).Run(context.Background())
|
||||
).Run(t.Context())
|
||||
require.Error(t, err)
|
||||
assert.ElementsMatch(t, []string{"error1", "error2", "error3"}, strings.Split(err.Error(), "\n"))
|
||||
assert.Equal(t, int32(3), i)
|
||||
|
@ -209,9 +209,9 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i)
|
||||
require.EqualError(t, m.Run(context.Background()), "runner manager already started")
|
||||
require.EqualError(t, m.Run(t.Context()), "runner manager already started")
|
||||
assert.Equal(t, int32(1), i)
|
||||
})
|
||||
|
||||
|
@ -221,7 +221,7 @@ func Test_RunnerManager(t *testing.T) {
|
|||
atomic.AddInt32(&i, 1)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, m.Run(context.Background()))
|
||||
require.NoError(t, m.Run(t.Context()))
|
||||
assert.Equal(t, int32(1), i)
|
||||
err := m.Add(func(ctx context.Context) error {
|
||||
atomic.AddInt32(&i, 1)
|
||||
|
|
|
@ -81,7 +81,7 @@ func decodeString(f reflect.Type, t reflect.Type, data any) (any, error) {
|
|||
if t.Implements(typeStringDecoder) {
|
||||
result = reflect.New(t.Elem()).Interface()
|
||||
decoder = result.(StringDecoder)
|
||||
} else if reflect.PtrTo(t).Implements(typeStringDecoder) {
|
||||
} else if reflect.PointerTo(t).Implements(typeStringDecoder) {
|
||||
result = reflect.New(t).Interface()
|
||||
decoder = result.(StringDecoder)
|
||||
}
|
||||
|
|
|
@ -52,6 +52,9 @@ func NewPool(ctx ...context.Context) *Pool {
|
|||
go func() {
|
||||
defer cancel()
|
||||
defer p.lock.RUnlock()
|
||||
//nolint:intrange
|
||||
// for loops are evaluated on every loop while range are evaluated over a snapshot of the slice as it
|
||||
// existed when the loop started
|
||||
for i := 0; i < len(p.pool); i++ {
|
||||
ch := p.pool[i]
|
||||
p.lock.RUnlock()
|
||||
|
|
|
@ -37,7 +37,7 @@ func Test_Pool(t *testing.T) {
|
|||
|
||||
t.Run("a cancelled context given to pool, should have pool cancelled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
pool := NewPool(ctx)
|
||||
select {
|
||||
|
@ -49,10 +49,10 @@ func Test_Pool(t *testing.T) {
|
|||
|
||||
t.Run("a cancelled context given to pool, given a new context, should still have pool cancelled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
pool := NewPool(ctx)
|
||||
pool.Add(context.Background())
|
||||
pool.Add(t.Context())
|
||||
select {
|
||||
case <-pool.Done():
|
||||
case <-time.After(time.Second):
|
||||
|
@ -65,13 +65,13 @@ func Test_Pool(t *testing.T) {
|
|||
var ctx [50]context.Context
|
||||
var cancel [50]context.CancelFunc
|
||||
|
||||
ctx[0], cancel[0] = context.WithCancel(context.Background())
|
||||
pool := NewPool(ctx[0])
|
||||
ctxPool := make([]context.Context, 0, 50)
|
||||
|
||||
for i := 1; i < 50; i++ {
|
||||
ctx[i], cancel[i] = context.WithCancel(context.Background())
|
||||
pool.Add(ctx[i])
|
||||
for i := range 50 {
|
||||
ctx[i], cancel[i] = context.WithCancel(t.Context())
|
||||
ctxPool = append(ctxPool, ctx[i])
|
||||
}
|
||||
pool := NewPool(ctxPool...)
|
||||
|
||||
//nolint:gosec
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
@ -80,7 +80,7 @@ func Test_Pool(t *testing.T) {
|
|||
cancel[i], cancel[j] = cancel[j], cancel[i]
|
||||
})
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
for i := range 50 {
|
||||
select {
|
||||
case <-pool.Done():
|
||||
t.Error("expected context to not be cancelled")
|
||||
|
@ -99,8 +99,8 @@ func Test_Pool(t *testing.T) {
|
|||
t.Run("pool size will not increase if the given contexts have been cancelled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx1, cancel1 := context.WithCancel(context.Background())
|
||||
ctx2, cancel2 := context.WithCancel(context.Background())
|
||||
ctx1, cancel1 := context.WithCancel(t.Context())
|
||||
ctx2, cancel2 := context.WithCancel(t.Context())
|
||||
pool := NewPool(ctx1, ctx2)
|
||||
assert.Equal(t, 2, pool.Size())
|
||||
|
||||
|
@ -111,19 +111,19 @@ func Test_Pool(t *testing.T) {
|
|||
case <-time.After(time.Second):
|
||||
t.Error("expected context pool to be cancelled")
|
||||
}
|
||||
pool.Add(context.Background())
|
||||
pool.Add(t.Context())
|
||||
assert.Equal(t, 2, pool.Size())
|
||||
})
|
||||
|
||||
t.Run("pool size will not increase if the pool has been closed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx1 := context.Background()
|
||||
ctx2 := context.Background()
|
||||
ctx1 := t.Context()
|
||||
ctx2 := t.Context()
|
||||
pool := NewPool(ctx1, ctx2)
|
||||
assert.Equal(t, 2, pool.Size())
|
||||
pool.Cancel()
|
||||
pool.Add(context.Background())
|
||||
pool.Add(t.Context())
|
||||
assert.Equal(t, 0, pool.Size())
|
||||
select {
|
||||
case <-pool.Done():
|
||||
|
@ -131,4 +131,24 @@ func Test_Pool(t *testing.T) {
|
|||
t.Error("expected context pool to be cancelled")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wait for added context to be closed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx1, cancel1 := context.WithCancel(t.Context())
|
||||
pool := NewPool(ctx1)
|
||||
|
||||
ctx2, cancel2 := context.WithCancel(t.Context())
|
||||
pool.Add(ctx2)
|
||||
|
||||
assert.Equal(t, 2, pool.Size())
|
||||
cancel1()
|
||||
|
||||
select {
|
||||
case <-pool.Done():
|
||||
t.Error("expected context pool to not be cancelled")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
cancel2()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ func TestChainDelayIfStillRunning(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
started, done = j.Started(), j.Done()
|
||||
if started != 2 || done != 2 {
|
||||
c.Errorf("expected both jobs done, got %v %v", started, done) //nolint:testifylint
|
||||
c.Errorf("expected both jobs done, got %v %v", started, done)
|
||||
}
|
||||
}, 100*time.Millisecond, 10*time.Millisecond)
|
||||
})
|
||||
|
@ -230,7 +230,7 @@ func TestChainSkipIfStillRunning(t *testing.T) {
|
|||
var j countJob
|
||||
j.delay = 10 * time.Millisecond
|
||||
wrappedJob := NewChain(SkipIfStillRunning(DiscardLogger)).Then(&j)
|
||||
for i := 0; i < 11; i++ {
|
||||
for range 11 {
|
||||
go wrappedJob.Run()
|
||||
}
|
||||
assert.Eventually(t, j.clock.HasWaiters, 50*time.Millisecond, 10*time.Millisecond)
|
||||
|
@ -248,7 +248,7 @@ func TestChainSkipIfStillRunning(t *testing.T) {
|
|||
chain := NewChain(SkipIfStillRunning(DiscardLogger))
|
||||
wrappedJob1 := chain.Then(&j1)
|
||||
wrappedJob2 := chain.Then(&j2)
|
||||
for i := 0; i < 11; i++ {
|
||||
for range 11 {
|
||||
go wrappedJob1.Run()
|
||||
go wrappedJob2.Run()
|
||||
}
|
||||
|
|
|
@ -14,31 +14,23 @@ You can check the original license at:
|
|||
https://github.com/robfig/cron/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
//nolint
|
||||
package cron
|
||||
|
||||
import "time"
|
||||
|
||||
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
|
||||
// It does not support jobs more frequent than once a second.
|
||||
type ConstantDelaySchedule struct {
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
// Every returns a crontab Schedule that activates once every duration.
|
||||
// Delays of less than a second are not supported (will round up to 1 second).
|
||||
// Any fields less than a Second are truncated.
|
||||
func Every(duration time.Duration) ConstantDelaySchedule {
|
||||
if duration < time.Second {
|
||||
duration = time.Second
|
||||
}
|
||||
return ConstantDelaySchedule{
|
||||
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
|
||||
Delay: duration,
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next time this should be run.
|
||||
// This rounds so that the next activation time will be on the second.
|
||||
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
|
||||
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||
return t.Add(schedule.Delay)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ You can check the original license at:
|
|||
https://github.com/robfig/cron/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
//nolint
|
||||
package cron
|
||||
|
||||
import (
|
||||
|
@ -29,9 +28,12 @@ func TestConstantDelayNext(t *testing.T) {
|
|||
expected string
|
||||
}{
|
||||
// Simple cases
|
||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00:00.00000005 2012"},
|
||||
{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
|
||||
{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
|
||||
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:00.015 2012"},
|
||||
{"Mon Jul 9 14:45:00.015 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:00.030 2012"},
|
||||
{"Mon Jul 9 14:45:00.000000050 2012", 15 * time.Nanosecond, "Mon Jul 9 14:45:00.000000065 2012"},
|
||||
|
||||
// Wrap around hours
|
||||
{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
|
||||
|
@ -47,18 +49,6 @@ func TestConstantDelayNext(t *testing.T) {
|
|||
|
||||
// Wrap around minute, hour, day, month, and year
|
||||
{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
|
||||
|
||||
// Round to nearest second on the delay
|
||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
|
||||
// Round up to 1 second if the duration is less.
|
||||
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
|
||||
|
||||
// Round to nearest second when calculating the next time.
|
||||
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
|
||||
|
||||
// Round to nearest second for both.
|
||||
{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
|
|
|
@ -14,7 +14,6 @@ You can check the original license at:
|
|||
https://github.com/robfig/cron/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
//nolint:dupword
|
||||
package cron
|
||||
|
||||
import (
|
||||
|
@ -35,7 +34,7 @@ import (
|
|||
// for it to run. This amount is just slightly larger than 1 second to
|
||||
// compensate for a few milliseconds of runtime.
|
||||
//
|
||||
//nolint:revive
|
||||
|
||||
const OneSecond = 1*time.Second + 50*time.Millisecond
|
||||
|
||||
type syncWriter struct {
|
||||
|
@ -783,13 +782,68 @@ func TestMockClock(t *testing.T) {
|
|||
})
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
for i := 0; i <= 10; i++ {
|
||||
for range 11 {
|
||||
assert.Eventually(t, clk.HasWaiters, OneSecond, 10*time.Millisecond)
|
||||
clk.Step(1 * time.Second)
|
||||
}
|
||||
assert.Equal(t, int64(10), counter.Load())
|
||||
}
|
||||
|
||||
func TestMillisecond(t *testing.T) {
|
||||
clk := clocktesting.NewFakeClock(time.Now())
|
||||
cron := New(WithClock(clk))
|
||||
counter1ms := atomic.Int64{}
|
||||
counter15ms := atomic.Int64{}
|
||||
counter100ms := atomic.Int64{}
|
||||
|
||||
cron.AddFunc("@every 1ms", func() {
|
||||
counter1ms.Add(1)
|
||||
})
|
||||
cron.AddFunc("@every 15ms", func() {
|
||||
counter15ms.Add(1)
|
||||
})
|
||||
cron.AddFunc("@every 100ms", func() {
|
||||
counter100ms.Add(1)
|
||||
})
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
for range 1000 {
|
||||
assert.Eventually(t, clk.HasWaiters, OneSecond, 1*time.Millisecond)
|
||||
clk.Step(1 * time.Millisecond)
|
||||
}
|
||||
ctx := cron.Stop()
|
||||
<-ctx.Done()
|
||||
|
||||
assert.Equal(t, int64(1000), counter1ms.Load())
|
||||
assert.Equal(t, int64(66), counter15ms.Load())
|
||||
assert.Equal(t, int64(10), counter100ms.Load())
|
||||
}
|
||||
|
||||
func TestNanoseconds(t *testing.T) {
|
||||
clk := clocktesting.NewFakeClock(time.Now())
|
||||
cron := New(WithClock(clk))
|
||||
|
||||
counter100ns := atomic.Int64{}
|
||||
cron.AddFunc("@every 100ns", func() {
|
||||
counter100ns.Add(1)
|
||||
})
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
for range 500 {
|
||||
assert.Eventually(t, clk.HasWaiters, OneSecond, 1*time.Millisecond)
|
||||
clk.Step(5 * time.Nanosecond)
|
||||
}
|
||||
ctx := cron.Stop()
|
||||
<-ctx.Done()
|
||||
|
||||
// 500 * 5 ns = 2500 ns
|
||||
// 2500 every 100ns = 25
|
||||
assert.Equal(t, int64(25), counter100ns.Load())
|
||||
}
|
||||
|
||||
func TestMultiThreadedStartAndStop(*testing.T) {
|
||||
cron := New()
|
||||
go cron.Run()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//nolint
|
||||
/*
|
||||
This package is a fork of "github.com/robfig/cron/v3" that implements cron spec parser and job runner with support for mocking the time.
|
||||
|
||||
|
@ -36,7 +35,9 @@ them in their own goroutines.
|
|||
# Time mocking
|
||||
|
||||
import (
|
||||
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
|
||||
)
|
||||
|
||||
clk := clocktesting.NewFakeClock(time.Now())
|
||||
|
|
|
@ -59,7 +59,7 @@ func TestWithVerboseLogger(t *testing.T) {
|
|||
out := buf.String()
|
||||
if !strings.Contains(out, "schedule,") ||
|
||||
!strings.Contains(out, "run,") {
|
||||
c.Errorf("expected to see some actions, got: %v", out) //nolint:testifylint
|
||||
c.Errorf("expected to see some actions, got: %v", out)
|
||||
}
|
||||
}, time.Second, time.Millisecond*10)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ You can check the original license at:
|
|||
https://github.com/robfig/cron/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
//nolint
|
||||
package cron
|
||||
|
||||
import (
|
||||
|
@ -167,6 +166,8 @@ func TestParseSchedule(t *testing.T) {
|
|||
{standardParser, "CRON_TZ=UTC 5 * * * *", every5min(time.UTC)},
|
||||
{secondParser, "CRON_TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
|
||||
{secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
|
||||
{secondParser, "@every 5ms", ConstantDelaySchedule{5 * time.Millisecond}},
|
||||
{secondParser, "@every 5ns", ConstantDelaySchedule{5 * time.Nanosecond}},
|
||||
{secondParser, "@midnight", midnight(time.Local)},
|
||||
{secondParser, "TZ=UTC @midnight", midnight(time.UTC)},
|
||||
{secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)},
|
||||
|
|
|
@ -214,9 +214,9 @@ func (aead *aesCBCAEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]b
|
|||
}
|
||||
|
||||
// Computes the HMAC tag as per specs.
|
||||
func (aead aesCBCAEAD) hmacTag(h hash.Hash, additionalData, nonce, ciphertext []byte, l int) []byte {
|
||||
func (aead *aesCBCAEAD) hmacTag(h hash.Hash, additionalData, nonce, ciphertext []byte, l int) []byte {
|
||||
al := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(al, uint64(len(additionalData)<<3)) // In bits
|
||||
binary.BigEndian.PutUint64(al, uint64(len(additionalData)<<3)) // #nosec G115 // In bits
|
||||
|
||||
h.Write(additionalData)
|
||||
h.Write(nonce)
|
||||
|
|
|
@ -48,14 +48,14 @@ func Wrap(block cipher.Block, cek []byte) ([]byte, error) {
|
|||
copy(r[i], cek[i*8:])
|
||||
}
|
||||
|
||||
for j := 0; j <= 5; j++ {
|
||||
for j := range 6 {
|
||||
for i := 1; i <= n; i++ {
|
||||
b := arrConcat(a, r[i-1])
|
||||
block.Encrypt(b, b)
|
||||
|
||||
t := (n * j) + i
|
||||
tBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t))
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t)) // #nosec G115
|
||||
|
||||
copy(a, arrXor(b[:len(b)/2], tBytes))
|
||||
copy(r[i-1], b[len(b)/2:])
|
||||
|
@ -92,7 +92,7 @@ func Unwrap(block cipher.Block, cipherText []byte) ([]byte, error) {
|
|||
for i := n; i >= 1; i-- {
|
||||
t := (n * j) + i
|
||||
tBytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t))
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t)) // #nosec G115
|
||||
|
||||
b := arrConcat(arrXor(a, tBytes), r[i-1])
|
||||
block.Decrypt(b, b)
|
||||
|
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
//nolint:nosnakecase,stylecheck,revive
|
||||
//nolint:nosnakecase,stylecheck
|
||||
package crypto
|
||||
|
||||
import (
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// DecodePEMCertificatesChain takes a PEM-encoded x509 certificates byte array
|
||||
|
@ -35,7 +36,7 @@ func DecodePEMCertificatesChain(crtb []byte) ([]*x509.Certificate, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(certs)-1; i++ {
|
||||
for i := range len(certs) - 1 {
|
||||
if certs[i].CheckSignatureFrom(certs[i+1]) != nil {
|
||||
return nil, errors.New("certificate chain is not valid")
|
||||
}
|
||||
|
@ -187,3 +188,24 @@ func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) {
|
|||
return false, fmt.Errorf("unrecognised public key type: %T", a)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPEM loads a PEM-encoded file (certificate or key).
|
||||
func GetPEM(val string) ([]byte, error) {
|
||||
// If val is already a PEM-encoded string, return it as-is
|
||||
if IsValidPEM(val) {
|
||||
return []byte(val), nil
|
||||
}
|
||||
|
||||
// Assume it's a file
|
||||
pemBytes, err := os.ReadFile(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value is neither a valid file path or nor a valid PEM-encoded string: %w", err)
|
||||
}
|
||||
return pemBytes, nil
|
||||
}
|
||||
|
||||
// IsValidPEM validates the provided input has PEM formatted block.
|
||||
func IsValidPEM(val string) bool {
|
||||
block, _ := pem.Decode([]byte(val))
|
||||
return block != nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Dapr Authors
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
@ -16,6 +16,7 @@ package context
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
|
||||
|
||||
"github.com/dapr/kit/crypto/spiffe"
|
||||
|
@ -23,13 +24,48 @@ import (
|
|||
|
||||
type ctxkey int
|
||||
|
||||
const svidKey ctxkey = iota
|
||||
const (
|
||||
x509SvidKey ctxkey = iota
|
||||
jwtSvidKey
|
||||
)
|
||||
|
||||
// Deprecated: use WithX509 instead.
|
||||
// With adds the x509 SVID source from the SPIFFE object to the context.
|
||||
func With(ctx context.Context, spiffe *spiffe.SPIFFE) context.Context {
|
||||
return context.WithValue(ctx, svidKey, spiffe.SVIDSource())
|
||||
return context.WithValue(ctx, x509SvidKey, spiffe.X509SVIDSource())
|
||||
}
|
||||
|
||||
// Deprecated: use X509From instead.
|
||||
// From retrieves the x509 SVID source from the context.
|
||||
func From(ctx context.Context) (x509svid.Source, bool) {
|
||||
svid, ok := ctx.Value(svidKey).(x509svid.Source)
|
||||
svid, ok := ctx.Value(x509SvidKey).(x509svid.Source)
|
||||
return svid, ok
|
||||
}
|
||||
|
||||
// WithX509 adds an x509 SVID source to the context.
|
||||
func WithX509(ctx context.Context, source x509svid.Source) context.Context {
|
||||
return context.WithValue(ctx, x509SvidKey, source)
|
||||
}
|
||||
|
||||
// WithJWT adds a JWT SVID source to the context.
|
||||
func WithJWT(ctx context.Context, source jwtsvid.Source) context.Context {
|
||||
return context.WithValue(ctx, jwtSvidKey, source)
|
||||
}
|
||||
|
||||
// X509From retrieves the x509 SVID source from the context.
|
||||
func X509From(ctx context.Context) (x509svid.Source, bool) {
|
||||
svid, ok := ctx.Value(x509SvidKey).(x509svid.Source)
|
||||
return svid, ok
|
||||
}
|
||||
|
||||
// JWTFrom retrieves the JWT SVID source from the context.
|
||||
func JWTFrom(ctx context.Context) (jwtsvid.Source, bool) {
|
||||
svid, ok := ctx.Value(jwtSvidKey).(jwtsvid.Source)
|
||||
return svid, ok
|
||||
}
|
||||
|
||||
// WithSpiffe adds both X509 and JWT SVID sources to the context.
|
||||
func WithSpiffe(ctx context.Context, spiffe *spiffe.SPIFFE) context.Context {
|
||||
ctx = WithX509(ctx, spiffe.X509SVIDSource())
|
||||
return WithJWT(ctx, spiffe.JWTSVIDSource())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockX509Source struct{}
|
||||
|
||||
func (m *mockX509Source) GetX509SVID() (*x509svid.SVID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type mockJWTSource struct{}
|
||||
|
||||
func (m *mockJWTSource) FetchJWTSVID(context.Context, jwtsvid.Params) (*jwtsvid.SVID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestWithX509FromX509(t *testing.T) {
|
||||
source := &mockX509Source{}
|
||||
ctx := WithX509(t.Context(), source)
|
||||
|
||||
retrieved, ok := X509From(ctx)
|
||||
assert.True(t, ok, "Failed to retrieve X509 source from context")
|
||||
assert.Equal(t, x509svid.Source(source), retrieved, "Retrieved source does not match the original source")
|
||||
}
|
||||
|
||||
func TestWithJWTFromJWT(t *testing.T) {
|
||||
source := &mockJWTSource{}
|
||||
ctx := WithJWT(t.Context(), source)
|
||||
|
||||
retrieved, ok := JWTFrom(ctx)
|
||||
assert.True(t, ok, "Failed to retrieve JWT source from context")
|
||||
assert.Equal(t, jwtsvid.Source(source), retrieved, "Retrieved source does not match the original source")
|
||||
}
|
||||
|
||||
func TestWithFrom(t *testing.T) {
|
||||
x509Source := &mockX509Source{}
|
||||
ctx := WithX509(t.Context(), x509Source)
|
||||
|
||||
// Should be able to retrieve using the legacy From function
|
||||
retrieved, ok := From(ctx)
|
||||
assert.True(t, ok, "Failed to retrieve X509 source from context using legacy From")
|
||||
assert.Equal(t, x509svid.Source(x509Source), retrieved, "Retrieved source does not match the original source using legacy From")
|
||||
}
|
|
@ -25,6 +25,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
|
@ -34,8 +35,29 @@ import (
|
|||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
// renewalDivisor represents the divisor for calculating renewal time.
|
||||
// A value of 2 means renewal at 50% of the validity period.
|
||||
renewalDivisor = 2
|
||||
)
|
||||
|
||||
// SVIDResponse represents the response from the SVID request function,
|
||||
// containing both X.509 certificates and a JWT token.
|
||||
type SVIDResponse struct {
|
||||
X509Certificates []*x509.Certificate
|
||||
JWT *string
|
||||
}
|
||||
|
||||
// Identity contains both X.509 and JWT SVIDs for a workload.
|
||||
type Identity struct {
|
||||
X509SVID *x509svid.SVID
|
||||
JWTSVID *jwtsvid.SVID
|
||||
}
|
||||
|
||||
type (
|
||||
RequestSVIDFn func(context.Context, []byte) ([]*x509.Certificate, error)
|
||||
// RequestSVIDFn is the function type that requests SVIDs from a SPIFFE server,
|
||||
// returning both X.509 certificates and a JWT token.
|
||||
RequestSVIDFn func(context.Context, []byte) (*SVIDResponse, error)
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@ -51,11 +73,12 @@ type Options struct {
|
|||
TrustAnchors trustanchors.Interface
|
||||
}
|
||||
|
||||
// SPIFFE is a readable/writeable store of a SPIFFE X.509 SVID.
|
||||
// Used to manage a workload SVID, and share read-only interfaces to consumers.
|
||||
// SPIFFE is a readable/writeable store of SPIFFE SVID credentials.
|
||||
// Used to manage workload SVIDs, and share read-only interfaces to consumers.
|
||||
type SPIFFE struct {
|
||||
currentSVID *x509svid.SVID
|
||||
requestSVIDFn RequestSVIDFn
|
||||
currentX509SVID *x509svid.SVID
|
||||
currentJWTSVID *jwtsvid.SVID
|
||||
requestSVIDFn RequestSVIDFn
|
||||
|
||||
dir *dir.Dir
|
||||
trustAnchors trustanchors.Interface
|
||||
|
@ -92,15 +115,16 @@ func (s *SPIFFE) Run(ctx context.Context) error {
|
|||
}
|
||||
|
||||
s.lock.Lock()
|
||||
s.log.Info("Fetching initial identity certificate")
|
||||
initialCert, err := s.fetchIdentityCertificate(ctx)
|
||||
s.log.Info("Fetching initial identity")
|
||||
initialIdentity, err := s.fetchIdentity(ctx)
|
||||
if err != nil {
|
||||
close(s.readyCh)
|
||||
s.lock.Unlock()
|
||||
return fmt.Errorf("failed to retrieve the initial identity certificate: %w", err)
|
||||
return fmt.Errorf("failed to retrieve the initial identity: %w", err)
|
||||
}
|
||||
|
||||
s.currentSVID = initialCert
|
||||
s.currentX509SVID = initialIdentity.X509SVID
|
||||
s.currentJWTSVID = initialIdentity.JWTSVID
|
||||
close(s.readyCh)
|
||||
s.lock.Unlock()
|
||||
|
||||
|
@ -121,28 +145,48 @@ func (s *SPIFFE) Ready(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
// runRotation starts up the manager responsible for renewing the workload
|
||||
// certificate. Receives the initial certificate to calculate the next rotation
|
||||
// time.
|
||||
// logIdentityInfo creates a log message with expiry details for both X.509 and JWT SVIDs
|
||||
func (s *SPIFFE) logIdentityInfo(prefix string, cert *x509.Certificate, jwtSVID *jwtsvid.SVID, renewTime *time.Time) {
|
||||
msg := prefix + "; cert expires on: %s"
|
||||
args := []any{cert.NotAfter.String()}
|
||||
|
||||
if jwtSVID != nil {
|
||||
msg += ", jwt expires on: %s"
|
||||
args = append(args, jwtSVID.Expiry.String())
|
||||
}
|
||||
|
||||
if renewTime != nil {
|
||||
msg += ", renewal at: %s"
|
||||
args = append(args, renewTime.String())
|
||||
}
|
||||
|
||||
s.log.Infof(msg, args...)
|
||||
}
|
||||
|
||||
// runRotation starts up the manager responsible for renewing the workload identity
|
||||
func (s *SPIFFE) runRotation(ctx context.Context) {
|
||||
defer s.log.Debug("stopping workload cert expiry watcher")
|
||||
defer s.log.Debug("stopping workload identity expiry watcher")
|
||||
|
||||
s.lock.RLock()
|
||||
cert := s.currentSVID.Certificates[0]
|
||||
cert := s.currentX509SVID.Certificates[0]
|
||||
jwtSVID := s.currentJWTSVID
|
||||
s.lock.RUnlock()
|
||||
renewTime := renewalTime(cert.NotBefore, cert.NotAfter)
|
||||
s.log.Infof("Starting workload cert expiry watcher; current cert expires on: %s, renewing at %s",
|
||||
cert.NotAfter.String(), renewTime.String())
|
||||
|
||||
renewTime := calculateRenewalTime(time.Now(), cert, jwtSVID)
|
||||
s.logIdentityInfo("Starting workload identity expiry watcher", cert, jwtSVID, renewTime)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.clock.After(min(time.Minute, renewTime.Sub(s.clock.Now()))):
|
||||
if s.clock.Now().Before(renewTime) {
|
||||
if s.clock.Now().Before(*renewTime) {
|
||||
continue
|
||||
}
|
||||
s.log.Infof("Renewing workload cert; current cert expires on: %s", cert.NotAfter.String())
|
||||
svid, err := s.fetchIdentityCertificate(ctx)
|
||||
|
||||
s.logIdentityInfo("Renewing workload identity", cert, jwtSVID, nil)
|
||||
|
||||
identity, err := s.fetchIdentity(ctx)
|
||||
if err != nil {
|
||||
s.log.Errorf("Error renewing identity certificate, trying again in 10 seconds: %s", err)
|
||||
s.log.Errorf("Error renewing identity, trying again in 10 seconds: %s", err)
|
||||
select {
|
||||
case <-s.clock.After(10 * time.Second):
|
||||
continue
|
||||
|
@ -150,12 +194,16 @@ func (s *SPIFFE) runRotation(ctx context.Context) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
s.lock.Lock()
|
||||
s.currentSVID = svid
|
||||
cert = svid.Certificates[0]
|
||||
s.currentX509SVID = identity.X509SVID
|
||||
s.currentJWTSVID = identity.JWTSVID
|
||||
cert = identity.X509SVID.Certificates[0]
|
||||
jwtSVID = identity.JWTSVID
|
||||
s.lock.Unlock()
|
||||
renewTime = renewalTime(cert.NotBefore, cert.NotAfter)
|
||||
s.log.Infof("Successfully renewed workload cert; new cert expires on: %s", cert.NotAfter.String())
|
||||
|
||||
renewTime = calculateRenewalTime(time.Now(), cert, jwtSVID)
|
||||
s.logIdentityInfo("Successfully renewed workload identity", cert, jwtSVID, renewTime)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
@ -163,8 +211,8 @@ func (s *SPIFFE) runRotation(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// fetchIdentityCertificate fetches a new SVID using the configured requester.
|
||||
func (s *SPIFFE) fetchIdentityCertificate(ctx context.Context) (*x509svid.SVID, error) {
|
||||
// Returns both X.509 SVID and JWT SVID (if available).
|
||||
func (s *SPIFFE) fetchIdentity(ctx context.Context) (*Identity, error) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate private key: %w", err)
|
||||
|
@ -175,27 +223,55 @@ func (s *SPIFFE) fetchIdentityCertificate(ctx context.Context) (*x509svid.SVID,
|
|||
return nil, fmt.Errorf("failed to create sidecar csr: %w", err)
|
||||
}
|
||||
|
||||
workloadcert, err := s.requestSVIDFn(ctx, csrDER)
|
||||
svidResponse, err := s.requestSVIDFn(ctx, csrDER)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(workloadcert) == 0 {
|
||||
if len(svidResponse.X509Certificates) == 0 {
|
||||
return nil, errors.New("no certificates received from sentry")
|
||||
}
|
||||
|
||||
spiffeID, err := x509svid.IDFromCert(workloadcert[0])
|
||||
spiffeID, err := x509svid.IDFromCert(svidResponse.X509Certificates[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing spiffe id from newly signed certificate: %w", err)
|
||||
}
|
||||
|
||||
identity := &Identity{
|
||||
X509SVID: &x509svid.SVID{
|
||||
ID: spiffeID,
|
||||
Certificates: svidResponse.X509Certificates,
|
||||
PrivateKey: key,
|
||||
},
|
||||
}
|
||||
|
||||
// If we have a JWT token, parse it and include it in the identity
|
||||
if svidResponse.JWT != nil {
|
||||
// we are using ParseInsecure here as the expectation is that the
|
||||
// requestSVIDFn will have already parsed and validate the JWT SVID
|
||||
// before returning it.
|
||||
//
|
||||
// we are parsing the token using our SPIFFE ID's trust domain
|
||||
// as the audience as we expect the issuer to always include
|
||||
// that as an audience since that ensures that the token is
|
||||
// valid for us and our trust domain.
|
||||
audiences := []string{spiffeID.TrustDomain().Name()}
|
||||
jwtSvid, err := jwtsvid.ParseInsecure(*svidResponse.JWT, audiences)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JWT SVID: %w", err)
|
||||
}
|
||||
|
||||
identity.JWTSVID = jwtSvid
|
||||
s.log.Infof("Successfully received JWT SVID with expiry: %s", jwtSvid.Expiry.String())
|
||||
}
|
||||
|
||||
if s.dir != nil {
|
||||
pkPEM, err := pem.EncodePrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPEM, err := pem.EncodeX509Chain(workloadcert)
|
||||
certPEM, err := pem.EncodeX509Chain(svidResponse.X509Certificates)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -205,27 +281,72 @@ func (s *SPIFFE) fetchIdentityCertificate(ctx context.Context) (*x509svid.SVID,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.dir.Write(map[string][]byte{
|
||||
files := map[string][]byte{
|
||||
"key.pem": pkPEM,
|
||||
"cert.pem": certPEM,
|
||||
"ca.pem": td,
|
||||
}); err != nil {
|
||||
}
|
||||
|
||||
if svidResponse.JWT != nil {
|
||||
files["jwt_svid.token"] = []byte(*svidResponse.JWT)
|
||||
}
|
||||
|
||||
if err := s.dir.Write(files); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &x509svid.SVID{
|
||||
ID: spiffeID,
|
||||
Certificates: workloadcert,
|
||||
PrivateKey: key,
|
||||
}, nil
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (s *SPIFFE) SVIDSource() x509svid.Source {
|
||||
func (s *SPIFFE) X509SVIDSource() x509svid.Source {
|
||||
return &svidSource{spiffe: s}
|
||||
}
|
||||
|
||||
func (s *SPIFFE) JWTSVIDSource() jwtsvid.Source {
|
||||
return &svidSource{spiffe: s}
|
||||
}
|
||||
|
||||
// renewalTime is 50% through the certificate validity period.
|
||||
func renewalTime(notBefore, notAfter time.Time) time.Time {
|
||||
return notBefore.Add(notAfter.Sub(notBefore) / 2)
|
||||
return notBefore.Add(notAfter.Sub(notBefore) / renewalDivisor)
|
||||
}
|
||||
|
||||
// calculateRenewalTime returns the earlier renewal time between the X.509 certificate
|
||||
// and JWT SVID (if available) to ensure timely renewal.
|
||||
func calculateRenewalTime(now time.Time, cert *x509.Certificate, jwtSVID *jwtsvid.SVID) *time.Time {
|
||||
certRenewal := renewalTime(cert.NotBefore, cert.NotAfter)
|
||||
|
||||
if jwtSVID == nil {
|
||||
return &certRenewal
|
||||
}
|
||||
|
||||
jwtRenewal := now.Add(jwtSVID.Expiry.Sub(now) / renewalDivisor)
|
||||
|
||||
if jwtRenewal.Before(certRenewal) {
|
||||
return &jwtRenewal
|
||||
}
|
||||
return &certRenewal
|
||||
}
|
||||
|
||||
// audiencesMatch checks if the SVID audiences contain all the requested audiences
|
||||
func audiencesMatch(svidAudiences []string, requestedAudiences []string) bool {
|
||||
if len(requestedAudiences) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Create a map for faster lookup
|
||||
audienceMap := make(map[string]struct{}, len(svidAudiences))
|
||||
for _, audience := range svidAudiences {
|
||||
audienceMap[audience] = struct{}{}
|
||||
}
|
||||
|
||||
// Check if all requested audiences are in the SVID
|
||||
for _, requested := range requestedAudiences {
|
||||
if _, ok := audienceMap[requested]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
|
@ -39,16 +40,82 @@ func Test_renewalTime(t *testing.T) {
|
|||
assert.Equal(t, in30, renewalTime(now, in1Min))
|
||||
}
|
||||
|
||||
func Test_calculateRenewalTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
certShort := &x509.Certificate{
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(10 * time.Hour),
|
||||
}
|
||||
|
||||
certLong := &x509.Certificate{
|
||||
NotBefore: now,
|
||||
NotAfter: now.Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
// Expected renewal times for certificates (50% of validity period)
|
||||
certShortRenewal := now.Add(5 * time.Hour)
|
||||
|
||||
// Create JWT SVIDs with different expiry times
|
||||
jwtEarlier := &jwtsvid.SVID{
|
||||
Expiry: now.Add(8 * time.Hour),
|
||||
}
|
||||
|
||||
jwtLater := &jwtsvid.SVID{
|
||||
Expiry: now.Add(30 * time.Hour),
|
||||
}
|
||||
|
||||
// Expected JWT renewal time (50% of remaining time)
|
||||
jwtEarlierRenewal := now.Add(4 * time.Hour)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cert *x509.Certificate
|
||||
jwt *jwtsvid.SVID
|
||||
expected time.Time
|
||||
}{
|
||||
{
|
||||
name: "Certificate only",
|
||||
cert: certShort,
|
||||
jwt: nil,
|
||||
expected: certShortRenewal,
|
||||
},
|
||||
{
|
||||
name: "Certificate and JWT, JWT earlier",
|
||||
cert: certLong,
|
||||
jwt: jwtEarlier,
|
||||
expected: jwtEarlierRenewal,
|
||||
},
|
||||
{
|
||||
name: "Certificate and JWT, Certificate earlier",
|
||||
cert: certShort,
|
||||
jwt: jwtLater,
|
||||
expected: certShortRenewal,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := calculateRenewalTime(now, tt.cert, tt.jwt)
|
||||
|
||||
assert.WithinDuration(t, tt.expected, *actual, time.Millisecond,
|
||||
"Renewal time does not match expected value")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Run("should return error multiple Runs are called", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{
|
||||
LeafID: spiffeid.RequireFromString("spiffe://example.com/foo/bar"),
|
||||
})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
s := New(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
RequestSVIDFn: func(context.Context, []byte) ([]*x509.Certificate, error) {
|
||||
return []*x509.Certificate{pki.LeafCert}, nil
|
||||
RequestSVIDFn: func(context.Context, []byte) (*SVIDResponse, error) {
|
||||
return &SVIDResponse{
|
||||
X509Certificates: []*x509.Certificate{pki.LeafCert},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -79,12 +146,12 @@ func Test_Run(t *testing.T) {
|
|||
t.Run("should return error if initial fetch errors", func(t *testing.T) {
|
||||
s := New(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
RequestSVIDFn: func(context.Context, []byte) ([]*x509.Certificate, error) {
|
||||
RequestSVIDFn: func(context.Context, []byte) (*SVIDResponse, error) {
|
||||
return nil, errors.New("this is an error")
|
||||
},
|
||||
})
|
||||
|
||||
require.Error(t, s.Run(context.Background()))
|
||||
require.Error(t, s.Run(t.Context()))
|
||||
})
|
||||
|
||||
t.Run("should renew certificate when it has expired", func(t *testing.T) {
|
||||
|
@ -95,27 +162,29 @@ func Test_Run(t *testing.T) {
|
|||
var fetches atomic.Int32
|
||||
s := New(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
RequestSVIDFn: func(context.Context, []byte) ([]*x509.Certificate, error) {
|
||||
RequestSVIDFn: func(context.Context, []byte) (*SVIDResponse, error) {
|
||||
fetches.Add(1)
|
||||
return []*x509.Certificate{pki.LeafCert}, nil
|
||||
return &SVIDResponse{
|
||||
X509Certificates: []*x509.Certificate{pki.LeafCert},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
now := time.Now()
|
||||
clock := clocktesting.NewFakeClock(now)
|
||||
s.clock = clock
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
select {
|
||||
case <-s.readyCh:
|
||||
assert.Fail(t, "readyCh should not be closed")
|
||||
default:
|
||||
}
|
||||
|
||||
errCh <- s.Run(ctx)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-s.readyCh:
|
||||
assert.Fail(t, "readyCh should not be closed")
|
||||
default:
|
||||
}
|
||||
|
||||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond)
|
||||
assert.Equal(t, int32(1), fetches.Load())
|
||||
|
||||
|
@ -144,27 +213,29 @@ func Test_Run(t *testing.T) {
|
|||
var fetches atomic.Int32
|
||||
s := New(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
RequestSVIDFn: func(context.Context, []byte) ([]*x509.Certificate, error) {
|
||||
RequestSVIDFn: func(context.Context, []byte) (*SVIDResponse, error) {
|
||||
fetches.Add(1)
|
||||
return respCert, respErr
|
||||
return &SVIDResponse{
|
||||
X509Certificates: respCert,
|
||||
}, respErr
|
||||
},
|
||||
})
|
||||
now := time.Now()
|
||||
clock := clocktesting.NewFakeClock(now)
|
||||
s.clock = clock
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
select {
|
||||
case <-s.readyCh:
|
||||
assert.Fail(t, "readyCh should not be closed")
|
||||
default:
|
||||
}
|
||||
|
||||
errCh <- s.Run(ctx)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-s.readyCh:
|
||||
assert.Fail(t, "readyCh should not be closed")
|
||||
default:
|
||||
}
|
||||
|
||||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond)
|
||||
assert.Equal(t, int32(1), fetches.Load())
|
||||
|
||||
|
|
|
@ -14,27 +14,81 @@ limitations under the License.
|
|||
package spiffe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
|
||||
)
|
||||
|
||||
// svidSource is an implementation of the Go spiffe x509svid Source interface.
|
||||
var (
|
||||
errNoX509SVIDAvailable = errors.New("no X509 SVID available")
|
||||
errNoJWTSVIDAvailable = errors.New("no JWT SVID available")
|
||||
errAudienceRequired = errors.New("JWT audience is required")
|
||||
)
|
||||
|
||||
// svidSource is an implementation of both go-spiffe x509svid.Source and jwtsvid.Source interfaces.
|
||||
type svidSource struct {
|
||||
spiffe *SPIFFE
|
||||
}
|
||||
|
||||
// GetX509SVID returns the current X.509 certificate identity as a SPIFFE SVID.
|
||||
// Implements the go-spiffe x509 source interface.
|
||||
// Implements the go-spiffe x509svid.Source interface.
|
||||
func (s *svidSource) GetX509SVID() (*x509svid.SVID, error) {
|
||||
s.spiffe.lock.RLock()
|
||||
defer s.spiffe.lock.RUnlock()
|
||||
|
||||
<-s.spiffe.readyCh
|
||||
|
||||
svid := s.spiffe.currentSVID
|
||||
svid := s.spiffe.currentX509SVID
|
||||
if svid == nil {
|
||||
return nil, errors.New("no SVID available")
|
||||
return nil, errNoX509SVIDAvailable
|
||||
}
|
||||
|
||||
return svid, nil
|
||||
}
|
||||
|
||||
// audienceMismatchError is an error that contains information about mismatched audiences
|
||||
type audienceMismatchError struct {
|
||||
expected []string
|
||||
actual []string
|
||||
}
|
||||
|
||||
func (e *audienceMismatchError) Error() string {
|
||||
return fmt.Sprintf("JWT SVID has different audiences than requested: expected %s, got %s",
|
||||
strings.Join(e.expected, ", "), strings.Join(e.actual, ", "))
|
||||
}
|
||||
|
||||
// FetchJWTSVID returns the current JWT SVID.
|
||||
// Implements the go-spiffe jwtsvid.Source interface.
|
||||
func (s *svidSource) FetchJWTSVID(ctx context.Context, params jwtsvid.Params) (*jwtsvid.SVID, error) {
|
||||
s.spiffe.lock.RLock()
|
||||
defer s.spiffe.lock.RUnlock()
|
||||
|
||||
if params.Audience == "" {
|
||||
return nil, errAudienceRequired
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-s.spiffe.readyCh:
|
||||
}
|
||||
|
||||
svid := s.spiffe.currentJWTSVID
|
||||
if svid == nil {
|
||||
return nil, errNoJWTSVIDAvailable
|
||||
}
|
||||
|
||||
// verify that the audience being requested is the same as the audience in the SVID
|
||||
// WARN: we do not check extra audiences here.
|
||||
if !audiencesMatch(svid.Audience, []string{params.Audience}) {
|
||||
return nil, &audienceMismatchError{
|
||||
expected: []string{params.Audience},
|
||||
actual: svid.Audience,
|
||||
}
|
||||
}
|
||||
|
||||
return svid, nil
|
||||
|
|
|
@ -14,11 +14,177 @@ limitations under the License.
|
|||
package spiffe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/jwtsvid"
|
||||
"github.com/spiffe/go-spiffe/v2/svid/x509svid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_svidSource(*testing.T) {
|
||||
var _ x509svid.Source = new(svidSource)
|
||||
var _ jwtsvid.Source = new(svidSource)
|
||||
}
|
||||
|
||||
// createMockJWTSVID creates a mock JWT SVID for testing
|
||||
func createMockJWTSVID(audiences []string) (*jwtsvid.SVID, error) {
|
||||
td, err := spiffeid.TrustDomainFromString("example.org")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := spiffeid.FromSegments(td, "workload")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svid := &jwtsvid.SVID{
|
||||
ID: id,
|
||||
Audience: audiences,
|
||||
Expiry: time.Now().Add(time.Hour),
|
||||
}
|
||||
|
||||
return svid, nil
|
||||
}
|
||||
|
||||
func TestFetchJWTSVID(t *testing.T) {
|
||||
t.Run("should return error when audience is empty", func(t *testing.T) {
|
||||
s := &svidSource{
|
||||
spiffe: &SPIFFE{
|
||||
readyCh: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
},
|
||||
}
|
||||
close(s.spiffe.readyCh) // Mark as ready
|
||||
|
||||
svid, err := s.FetchJWTSVID(t.Context(), jwtsvid.Params{
|
||||
Audience: "",
|
||||
})
|
||||
|
||||
require.Nil(t, svid)
|
||||
require.ErrorIs(t, err, errAudienceRequired)
|
||||
})
|
||||
|
||||
t.Run("should return error when no JWT SVID available", func(t *testing.T) {
|
||||
s := &svidSource{
|
||||
spiffe: &SPIFFE{
|
||||
readyCh: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
currentJWTSVID: nil,
|
||||
},
|
||||
}
|
||||
close(s.spiffe.readyCh) // Mark as ready
|
||||
|
||||
svid, err := s.FetchJWTSVID(t.Context(), jwtsvid.Params{
|
||||
Audience: "test-audience",
|
||||
})
|
||||
|
||||
require.Nil(t, svid)
|
||||
require.ErrorIs(t, err, errNoJWTSVIDAvailable)
|
||||
})
|
||||
|
||||
t.Run("should return error when audience doesn't match", func(t *testing.T) {
|
||||
// Create a mock SVID with a specific audience
|
||||
mockJWTSVID, err := createMockJWTSVID([]string{"actual-audience"})
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &svidSource{
|
||||
spiffe: &SPIFFE{
|
||||
readyCh: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
currentJWTSVID: mockJWTSVID,
|
||||
},
|
||||
}
|
||||
close(s.spiffe.readyCh) // Mark as ready
|
||||
|
||||
svid, err := s.FetchJWTSVID(t.Context(), jwtsvid.Params{
|
||||
Audience: "requested-audience",
|
||||
})
|
||||
|
||||
require.Nil(t, svid)
|
||||
require.Error(t, err)
|
||||
|
||||
// Verify the specific error type and contents
|
||||
audienceErr, ok := err.(*audienceMismatchError)
|
||||
require.True(t, ok, "Expected audienceMismatchError")
|
||||
require.Equal(t, "JWT SVID has different audiences than requested: expected requested-audience, got actual-audience", audienceErr.Error())
|
||||
})
|
||||
|
||||
t.Run("should return JWT SVID when audience matches", func(t *testing.T) {
|
||||
mockJWTSVID, err := createMockJWTSVID([]string{"test-audience", "extra-audience"})
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &svidSource{
|
||||
spiffe: &SPIFFE{
|
||||
readyCh: make(chan struct{}),
|
||||
lock: sync.RWMutex{},
|
||||
currentJWTSVID: mockJWTSVID,
|
||||
},
|
||||
}
|
||||
close(s.spiffe.readyCh) // Mark as ready
|
||||
|
||||
svid, err := s.FetchJWTSVID(t.Context(), jwtsvid.Params{
|
||||
Audience: "test-audience",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockJWTSVID, svid)
|
||||
})
|
||||
|
||||
t.Run("should wait for readyCh before checking SVID", func(t *testing.T) {
|
||||
mockJWTSVID, err := createMockJWTSVID([]string{"test-audience"})
|
||||
require.NoError(t, err)
|
||||
|
||||
readyCh := make(chan struct{})
|
||||
s := &svidSource{
|
||||
spiffe: &SPIFFE{
|
||||
readyCh: readyCh,
|
||||
lock: sync.RWMutex{},
|
||||
currentJWTSVID: mockJWTSVID,
|
||||
},
|
||||
}
|
||||
|
||||
// Start goroutine to fetch SVID
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
resultCh := make(chan struct {
|
||||
svid *jwtsvid.SVID
|
||||
err error
|
||||
})
|
||||
|
||||
go func() {
|
||||
svid, err := s.FetchJWTSVID(ctx, jwtsvid.Params{
|
||||
Audience: "test-audience",
|
||||
})
|
||||
resultCh <- struct {
|
||||
svid *jwtsvid.SVID
|
||||
err error
|
||||
}{svid, err}
|
||||
}()
|
||||
|
||||
// require that fetch is blocked
|
||||
select {
|
||||
case <-resultCh:
|
||||
t.Fatal("FetchJWTSVID should be blocked until readyCh is closed")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// Expected behavior - fetch is blocked
|
||||
}
|
||||
|
||||
// Close readyCh to unblock fetch
|
||||
close(readyCh)
|
||||
|
||||
// Now fetch should complete
|
||||
select {
|
||||
case result := <-resultCh:
|
||||
require.NoError(t, result.err)
|
||||
require.NotNil(t, result.svid)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("FetchJWTSVID should have completed after readyCh was closed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,40 +11,52 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trustanchors
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/jwtbundle"
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"github.com/dapr/kit/concurrency"
|
||||
"github.com/dapr/kit/crypto/pem"
|
||||
"github.com/dapr/kit/crypto/spiffe/trustanchors"
|
||||
"github.com/dapr/kit/fswatcher"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
type OptionsFile struct {
|
||||
Log logger.Logger
|
||||
Path string
|
||||
var (
|
||||
// ErrTrustAnchorsClosed is returned when an operation is performed on closed trust anchors.
|
||||
ErrTrustAnchorsClosed = errors.New("trust anchors is closed")
|
||||
|
||||
// ErrFailedToReadTrustAnchorsFile is returned when the trust anchors file cannot be read.
|
||||
ErrFailedToReadTrustAnchorsFile = errors.New("failed to read trust anchors file")
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Log logger.Logger
|
||||
CAPath string
|
||||
JwksPath *string
|
||||
}
|
||||
|
||||
// file is a TrustAnchors implementation that uses a file as the source of trust
|
||||
// anchors. The trust anchors will be updated when the file changes.
|
||||
type file struct {
|
||||
log logger.Logger
|
||||
path string
|
||||
bundle *x509bundle.Bundle
|
||||
rootPEM []byte
|
||||
log logger.Logger
|
||||
caPath string
|
||||
jwksPath *string
|
||||
x509Bundle *x509bundle.Bundle
|
||||
jwtBundle *jwtbundle.Bundle
|
||||
rootPEM []byte
|
||||
|
||||
// fswatcherInterval is the interval at which the trust anchors file changes
|
||||
// are batched. Used for testing only, and 500ms otherwise.
|
||||
|
@ -65,17 +77,18 @@ type file struct {
|
|||
caEvent chan struct{}
|
||||
}
|
||||
|
||||
func FromFile(opts OptionsFile) Interface {
|
||||
func From(opts Options) trustanchors.Interface {
|
||||
return &file{
|
||||
fsWatcherInterval: time.Millisecond * 500,
|
||||
initFileWatchInterval: time.Second,
|
||||
|
||||
log: opts.Log,
|
||||
path: opts.Path,
|
||||
clock: clock.RealClock{},
|
||||
readyCh: make(chan struct{}),
|
||||
closeCh: make(chan struct{}),
|
||||
caEvent: make(chan struct{}),
|
||||
log: opts.Log,
|
||||
caPath: opts.CAPath,
|
||||
jwksPath: opts.JwksPath,
|
||||
clock: clock.RealClock{},
|
||||
readyCh: make(chan struct{}),
|
||||
closeCh: make(chan struct{}),
|
||||
caEvent: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,31 +100,39 @@ func (f *file) Run(ctx context.Context) error {
|
|||
defer close(f.closeCh)
|
||||
|
||||
for {
|
||||
_, err := os.Stat(f.path)
|
||||
if err == nil {
|
||||
break
|
||||
fs := []string{f.caPath}
|
||||
if f.jwksPath != nil {
|
||||
fs = append(fs, *f.jwksPath)
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
|
||||
if found, err := filesExist(fs...); err != nil {
|
||||
return err
|
||||
} else if found {
|
||||
break
|
||||
}
|
||||
|
||||
// Trust anchors file not be provided yet, wait.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("failed to find trust anchors file '%s': %w", f.path, ctx.Err())
|
||||
return fmt.Errorf("failed to find trust anchors file '%s': %w", f.caPath, ctx.Err())
|
||||
case <-f.clock.After(f.initFileWatchInterval):
|
||||
f.log.Warnf("Trust anchors file '%s' not found, waiting...", f.path)
|
||||
f.log.Warnf("Trust anchors file '%s' not found, waiting...", f.caPath)
|
||||
}
|
||||
}
|
||||
|
||||
f.log.Infof("Trust anchors file '%s' found", f.path)
|
||||
f.log.Infof("Trust anchors file '%s' found", f.caPath)
|
||||
|
||||
if err := f.updateAnchors(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targets := []string{f.caPath}
|
||||
if f.jwksPath != nil {
|
||||
targets = append(targets, *f.jwksPath)
|
||||
}
|
||||
|
||||
fs, err := fswatcher.New(fswatcher.Options{
|
||||
Targets: []string{filepath.Dir(f.path)},
|
||||
Targets: targets,
|
||||
Interval: &f.fsWatcherInterval,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -120,7 +141,11 @@ func (f *file) Run(ctx context.Context) error {
|
|||
|
||||
close(f.readyCh)
|
||||
|
||||
f.log.Infof("Watching trust anchors file '%s' for changes", f.path)
|
||||
f.log.Infof("Watching trust anchors file '%s' for changes", f.caPath)
|
||||
if f.jwksPath != nil {
|
||||
f.log.Infof("Watching JWT bundle file '%s' for changes", f.jwksPath)
|
||||
}
|
||||
|
||||
return concurrency.NewRunnerManager(
|
||||
func(ctx context.Context) error {
|
||||
return fs.Run(ctx, f.caEvent)
|
||||
|
@ -134,7 +159,7 @@ func (f *file) Run(ctx context.Context) error {
|
|||
f.log.Info("Trust anchors file changed, reloading trust anchors")
|
||||
|
||||
if err = f.updateAnchors(ctx); err != nil {
|
||||
return fmt.Errorf("failed to read trust anchors file '%s': %v", f.path, err)
|
||||
return fmt.Errorf("%w: '%s': %v", ErrFailedToReadTrustAnchorsFile, f.caPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +172,7 @@ func (f *file) CurrentTrustAnchors(ctx context.Context) ([]byte, error) {
|
|||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-f.closeCh:
|
||||
return nil, errors.New("trust anchors is closed")
|
||||
return nil, ErrTrustAnchorsClosed
|
||||
case <-f.readyCh:
|
||||
}
|
||||
|
||||
|
@ -162,9 +187,9 @@ func (f *file) updateAnchors(ctx context.Context) error {
|
|||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
rootPEMs, err := os.ReadFile(f.path)
|
||||
rootPEMs, err := os.ReadFile(f.caPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read trust anchors file '%s': %w", f.path, err)
|
||||
return fmt.Errorf("failed to read trust anchors file '%s': %w", f.caPath, err)
|
||||
}
|
||||
|
||||
trustAnchorCerts, err := pem.DecodePEMCertificates(rootPEMs)
|
||||
|
@ -173,7 +198,20 @@ func (f *file) updateAnchors(ctx context.Context) error {
|
|||
}
|
||||
|
||||
f.rootPEM = rootPEMs
|
||||
f.bundle = x509bundle.FromX509Authorities(spiffeid.TrustDomain{}, trustAnchorCerts)
|
||||
f.x509Bundle = x509bundle.FromX509Authorities(spiffeid.TrustDomain{}, trustAnchorCerts)
|
||||
|
||||
if f.jwksPath != nil {
|
||||
jwks, err := os.ReadFile(*f.jwksPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read JWT bundle file '%s': %w", *f.jwksPath, err)
|
||||
}
|
||||
|
||||
jwtBundle, err := jwtbundle.Parse(spiffeid.TrustDomain{}, jwks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse JWT bundle: %w", err)
|
||||
}
|
||||
f.jwtBundle = jwtBundle
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
@ -195,13 +233,26 @@ func (f *file) updateAnchors(ctx context.Context) error {
|
|||
func (f *file) GetX509BundleForTrustDomain(_ spiffeid.TrustDomain) (*x509bundle.Bundle, error) {
|
||||
select {
|
||||
case <-f.closeCh:
|
||||
return nil, errors.New("trust anchors is closed")
|
||||
return nil, ErrTrustAnchorsClosed
|
||||
case <-f.readyCh:
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
bundle := f.bundle
|
||||
bundle := f.x509Bundle
|
||||
return bundle, nil
|
||||
}
|
||||
|
||||
func (f *file) GetJWTBundleForTrustDomain(_ spiffeid.TrustDomain) (*jwtbundle.Bundle, error) {
|
||||
select {
|
||||
case <-f.closeCh:
|
||||
return nil, ErrTrustAnchorsClosed
|
||||
case <-f.readyCh:
|
||||
}
|
||||
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
bundle := f.jwtBundle
|
||||
return bundle, nil
|
||||
}
|
||||
|
||||
|
@ -231,3 +282,19 @@ func (f *file) Watch(ctx context.Context, ch chan<- []byte) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filesExist(paths ...string) (bool, error) {
|
||||
for _, path := range paths {
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to stat file '%s': %w", path, err)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trustanchors
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -31,15 +31,15 @@ import (
|
|||
func TestFile_Run(t *testing.T) {
|
||||
t.Run("if Run multiple times, expect error", func(t *testing.T) {
|
||||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
f.initFileWatchInterval = time.Millisecond
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(ctx)
|
||||
|
@ -74,15 +74,15 @@ func TestFile_Run(t *testing.T) {
|
|||
t.Run("if file is not found and context cancelled, should return ctx.Err", func(t *testing.T) {
|
||||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
f.initFileWatchInterval = time.Millisecond
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(ctx)
|
||||
|
@ -102,9 +102,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, nil, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -112,7 +112,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -127,9 +127,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, []byte("garbage data"), 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -137,7 +137,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -154,9 +154,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, root, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -164,7 +164,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -180,9 +180,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, pki.RootCertPEM, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -190,7 +190,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -199,7 +199,7 @@ func TestFile_Run(t *testing.T) {
|
|||
assert.Fail(t, "expected to be ready in time")
|
||||
}
|
||||
|
||||
b, err := f.CurrentTrustAnchors(context.Background())
|
||||
b, err := f.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, b)
|
||||
})
|
||||
|
@ -211,9 +211,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, root, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -221,7 +221,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -230,7 +230,7 @@ func TestFile_Run(t *testing.T) {
|
|||
assert.Fail(t, "expected to be ready in time")
|
||||
}
|
||||
|
||||
b, err := f.CurrentTrustAnchors(context.Background())
|
||||
b, err := f.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, root, b)
|
||||
})
|
||||
|
@ -242,9 +242,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, roots, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -252,7 +252,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -261,7 +261,7 @@ func TestFile_Run(t *testing.T) {
|
|||
assert.Fail(t, "expected to be ready in time")
|
||||
}
|
||||
|
||||
b, err := f.CurrentTrustAnchors(context.Background())
|
||||
b, err := f.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, roots, b)
|
||||
})
|
||||
|
@ -273,9 +273,9 @@ func TestFile_Run(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, roots, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -284,7 +284,7 @@ func TestFile_Run(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(context.Background())
|
||||
errCh <- f.Run(t.Context())
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -311,15 +311,15 @@ func TestFile_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
root := append(pki.RootCertPEM, []byte("garbage data")...)
|
||||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, root, 0o600))
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
go func() {
|
||||
errCh <- ta.Run(ctx)
|
||||
}()
|
||||
|
@ -337,7 +337,7 @@ func TestFile_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bundle, err := f.GetX509BundleForTrustDomain(trustDomain1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, f.bundle, bundle)
|
||||
assert.Equal(t, f.x509Bundle, bundle)
|
||||
b1, err := bundle.Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, b1)
|
||||
|
@ -346,7 +346,7 @@ func TestFile_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bundle, err = f.GetX509BundleForTrustDomain(trustDomain2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, f.bundle, bundle)
|
||||
assert.Equal(t, f.x509Bundle, bundle)
|
||||
b2, err := bundle.Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, b2)
|
||||
|
@ -359,23 +359,24 @@ func TestFile_Watch(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, pki.RootCertPEM, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
f.initFileWatchInterval = time.Millisecond
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
go func() {
|
||||
errCh <- f.Run(ctx)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running
|
||||
|
||||
watchDone := make(chan struct{})
|
||||
go func() {
|
||||
ta.Watch(context.Background(), make(chan []byte))
|
||||
ta.Watch(t.Context(), make(chan []byte))
|
||||
close(watchDone)
|
||||
}()
|
||||
|
||||
|
@ -400,22 +401,23 @@ func TestFile_Watch(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, pki.RootCertPEM, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
f.initFileWatchInterval = time.Millisecond
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx1, cancel1 := context.WithCancel(context.Background())
|
||||
ctx1, cancel1 := context.WithCancel(t.Context())
|
||||
go func() {
|
||||
errCh <- f.Run(ctx1)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running
|
||||
|
||||
watchDone := make(chan struct{})
|
||||
ctx2, cancel2 := context.WithCancel(context.Background())
|
||||
ctx2, cancel2 := context.WithCancel(t.Context())
|
||||
go func() {
|
||||
ta.Watch(ctx2, make(chan []byte))
|
||||
close(watchDone)
|
||||
|
@ -446,9 +448,9 @@ func TestFile_Watch(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, pki1.RootCertPEM, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
|
@ -456,10 +458,11 @@ func TestFile_Watch(t *testing.T) {
|
|||
f.fsWatcherInterval = time.Millisecond
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
go func() {
|
||||
errCh <- f.Run(ctx)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running
|
||||
|
||||
select {
|
||||
case <-f.readyCh:
|
||||
|
@ -470,11 +473,11 @@ func TestFile_Watch(t *testing.T) {
|
|||
watchDone1, watchDone2 := make(chan struct{}), make(chan struct{})
|
||||
tCh1, tCh2 := make(chan []byte), make(chan []byte)
|
||||
go func() {
|
||||
ta.Watch(context.Background(), tCh1)
|
||||
ta.Watch(t.Context(), tCh1)
|
||||
close(watchDone1)
|
||||
}()
|
||||
go func() {
|
||||
ta.Watch(context.Background(), tCh2)
|
||||
ta.Watch(t.Context(), tCh2)
|
||||
close(watchDone2)
|
||||
}()
|
||||
|
||||
|
@ -529,26 +532,28 @@ func TestFile_CurrentTrustAnchors(t *testing.T) {
|
|||
tmp := filepath.Join(t.TempDir(), "ca.crt")
|
||||
require.NoError(t, os.WriteFile(tmp, pki1.RootCertPEM, 0o600))
|
||||
|
||||
ta := FromFile(OptionsFile{
|
||||
Log: logger.NewLogger("test"),
|
||||
Path: tmp,
|
||||
ta := From(Options{
|
||||
Log: logger.NewLogger("test"),
|
||||
CAPath: tmp,
|
||||
})
|
||||
f, ok := ta.(*file)
|
||||
require.True(t, ok)
|
||||
f.initFileWatchInterval = time.Millisecond
|
||||
f.fsWatcherInterval = time.Millisecond
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- f.Run(ctx)
|
||||
}()
|
||||
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running
|
||||
//nolint:gocritic
|
||||
roots := append(pki1.RootCertPEM, pki2.RootCertPEM...)
|
||||
require.NoError(t, os.WriteFile(tmp, roots, 0o600))
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure the file watcher has time to pick up the change
|
||||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pem, err := ta.CurrentTrustAnchors(context.Background())
|
||||
pem, err := ta.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(c, roots, pem)
|
||||
}, time.Second, time.Millisecond)
|
||||
|
@ -556,8 +561,9 @@ func TestFile_CurrentTrustAnchors(t *testing.T) {
|
|||
//nolint:gocritic
|
||||
roots = append(pki1.RootCertPEM, append(pki2.RootCertPEM, pki3.RootCertPEM...)...)
|
||||
require.NoError(t, os.WriteFile(tmp, roots, 0o600))
|
||||
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure the file watcher has time to pick up the change
|
||||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pem, err := ta.CurrentTrustAnchors(context.Background())
|
||||
pem, err := ta.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(c, roots, pem)
|
||||
}, time.Second, time.Millisecond)
|
|
@ -11,16 +11,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trustanchors
|
||||
package multi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/jwtbundle"
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
|
||||
"github.com/dapr/kit/concurrency"
|
||||
"github.com/dapr/kit/crypto/spiffe/trustanchors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -28,17 +30,17 @@ var (
|
|||
ErrTrustDomainNotFound = errors.New("trust domain not found")
|
||||
)
|
||||
|
||||
type OptionsMulti struct {
|
||||
TrustAnchors map[spiffeid.TrustDomain]Interface
|
||||
type Options struct {
|
||||
TrustAnchors map[spiffeid.TrustDomain]trustanchors.Interface
|
||||
}
|
||||
|
||||
// multi is a TrustAnchors implementation which uses multiple trust anchors
|
||||
// which are indexed by trust domain.
|
||||
type multi struct {
|
||||
trustAnchors map[spiffeid.TrustDomain]Interface
|
||||
trustAnchors map[spiffeid.TrustDomain]trustanchors.Interface
|
||||
}
|
||||
|
||||
func FromMulti(opts OptionsMulti) Interface {
|
||||
func From(opts Options) trustanchors.Interface {
|
||||
return &multi{
|
||||
trustAnchors: opts.TrustAnchors,
|
||||
}
|
||||
|
@ -69,6 +71,16 @@ func (m *multi) GetX509BundleForTrustDomain(td spiffeid.TrustDomain) (*x509bundl
|
|||
return nil, ErrTrustDomainNotFound
|
||||
}
|
||||
|
||||
func (m *multi) GetJWTBundleForTrustDomain(td spiffeid.TrustDomain) (*jwtbundle.Bundle, error) {
|
||||
for tad, ta := range m.trustAnchors {
|
||||
if td.Compare(tad) == 0 {
|
||||
return ta.GetJWTBundleForTrustDomain(td)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrTrustDomainNotFound
|
||||
}
|
||||
|
||||
func (m *multi) Watch(context.Context, chan<- []byte) {
|
||||
return
|
||||
}
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trustanchors
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -19,31 +19,52 @@ import (
|
|||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/jwtbundle"
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
|
||||
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
||||
|
||||
"github.com/dapr/kit/crypto/pem"
|
||||
"github.com/dapr/kit/crypto/spiffe/trustanchors"
|
||||
)
|
||||
|
||||
// static is a TrustAcnhors implementation that uses a static list of trust
|
||||
// anchors.
|
||||
type static struct {
|
||||
bundle *x509bundle.Bundle
|
||||
anchors []byte
|
||||
running atomic.Bool
|
||||
closeCh chan struct{}
|
||||
x509Bundle *x509bundle.Bundle
|
||||
jwtBundle *jwtbundle.Bundle
|
||||
anchors []byte
|
||||
running atomic.Bool
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func FromStatic(anchors []byte) (Interface, error) {
|
||||
trustAnchorCerts, err := pem.DecodePEMCertificates(anchors)
|
||||
type Options struct {
|
||||
Anchors []byte
|
||||
Jwks []byte
|
||||
}
|
||||
|
||||
func From(opts Options) (trustanchors.Interface, error) {
|
||||
// Create empty trust domain for now
|
||||
emptyTD := spiffeid.TrustDomain{}
|
||||
|
||||
var jwtBundle *jwtbundle.Bundle
|
||||
if opts.Jwks != nil {
|
||||
var err error
|
||||
jwtBundle, err = jwtbundle.Parse(emptyTD, opts.Jwks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JWT bundle: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
trustAnchorCerts, err := pem.DecodePEMCertificates(opts.Anchors)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode trust anchors: %w", err)
|
||||
}
|
||||
|
||||
return &static{
|
||||
anchors: anchors,
|
||||
bundle: x509bundle.FromX509Authorities(spiffeid.TrustDomain{}, trustAnchorCerts),
|
||||
closeCh: make(chan struct{}),
|
||||
anchors: opts.Anchors,
|
||||
x509Bundle: x509bundle.FromX509Authorities(emptyTD, trustAnchorCerts),
|
||||
jwtBundle: jwtBundle,
|
||||
closeCh: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -63,7 +84,11 @@ func (s *static) Run(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (s *static) GetX509BundleForTrustDomain(spiffeid.TrustDomain) (*x509bundle.Bundle, error) {
|
||||
return s.bundle, nil
|
||||
return s.x509Bundle, nil
|
||||
}
|
||||
|
||||
func (s *static) GetJWTBundleForTrustDomain(_ spiffeid.TrustDomain) (*jwtbundle.Bundle, error) {
|
||||
return s.jwtBundle, nil
|
||||
}
|
||||
|
||||
func (s *static) Watch(ctx context.Context, _ chan<- []byte) {
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package trustanchors
|
||||
package static
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -27,32 +27,32 @@ import (
|
|||
|
||||
func TestFromStatic(t *testing.T) {
|
||||
t.Run("empty root should return error", func(t *testing.T) {
|
||||
_, err := FromStatic(nil)
|
||||
_, err := From(Options{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("garbage data should return error", func(t *testing.T) {
|
||||
_, err := FromStatic([]byte("garbage data"))
|
||||
_, err := From(Options{Anchors: []byte("garbage data")})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("just garbage data should return error", func(t *testing.T) {
|
||||
_, err := FromStatic([]byte("garbage data"))
|
||||
_, err := From(Options{Anchors: []byte("garbage data")})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("garbage data in root should return error", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
root := pki.RootCertPEM[10:]
|
||||
_, err := FromStatic(root)
|
||||
_, err := From(Options{Anchors: root})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("single root should be correctly parsed", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
ta, err := FromStatic(pki.RootCertPEM)
|
||||
ta, err := From(Options{Anchors: pki.RootCertPEM})
|
||||
require.NoError(t, err)
|
||||
taPEM, err := ta.CurrentTrustAnchors(context.Background())
|
||||
taPEM, err := ta.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, taPEM)
|
||||
})
|
||||
|
@ -61,9 +61,9 @@ func TestFromStatic(t *testing.T) {
|
|||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
//nolint:gocritic
|
||||
root := append(pki.RootCertPEM, []byte("garbage data")...)
|
||||
ta, err := FromStatic(root)
|
||||
ta, err := From(Options{Anchors: root})
|
||||
require.NoError(t, err)
|
||||
taPEM, err := ta.CurrentTrustAnchors(context.Background())
|
||||
taPEM, err := ta.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, root, taPEM)
|
||||
})
|
||||
|
@ -72,9 +72,9 @@ func TestFromStatic(t *testing.T) {
|
|||
pki1, pki2 := test.GenPKI(t, test.PKIOptions{}), test.GenPKI(t, test.PKIOptions{})
|
||||
//nolint:gocritic
|
||||
roots := append(pki1.RootCertPEM, pki2.RootCertPEM...)
|
||||
ta, err := FromStatic(roots)
|
||||
ta, err := From(Options{Anchors: roots})
|
||||
require.NoError(t, err)
|
||||
taPEM, err := ta.CurrentTrustAnchors(context.Background())
|
||||
taPEM, err := ta.CurrentTrustAnchors(t.Context())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, roots, taPEM)
|
||||
})
|
||||
|
@ -85,7 +85,7 @@ func TestStatic_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
//nolint:gocritic
|
||||
root := append(pki.RootCertPEM, []byte("garbage data")...)
|
||||
ta, err := FromStatic(root)
|
||||
ta, err := From(Options{Anchors: root})
|
||||
require.NoError(t, err)
|
||||
s, ok := ta.(*static)
|
||||
require.True(t, ok)
|
||||
|
@ -94,7 +94,7 @@ func TestStatic_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bundle, err := s.GetX509BundleForTrustDomain(trustDomain1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, s.bundle, bundle)
|
||||
assert.Equal(t, s.x509Bundle, bundle)
|
||||
b1, err := bundle.Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, b1)
|
||||
|
@ -103,7 +103,7 @@ func TestStatic_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
bundle, err = s.GetX509BundleForTrustDomain(trustDomain2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, s.bundle, bundle)
|
||||
assert.Equal(t, s.x509Bundle, bundle)
|
||||
b2, err := bundle.Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, pki.RootCertPEM, b2)
|
||||
|
@ -113,12 +113,12 @@ func TestStatic_GetX509BundleForTrustDomain(t *testing.T) {
|
|||
func TestStatic_Run(t *testing.T) {
|
||||
t.Run("Run multiple times should return error", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
ta, err := FromStatic(pki.RootCertPEM)
|
||||
ta, err := From(Options{Anchors: pki.RootCertPEM})
|
||||
require.NoError(t, err)
|
||||
s, ok := ta.(*static)
|
||||
require.True(t, ok)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- s.Run(ctx)
|
||||
|
@ -154,10 +154,10 @@ func TestStatic_Run(t *testing.T) {
|
|||
func TestStatic_Watch(t *testing.T) {
|
||||
t.Run("should return when context is cancelled", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
ta, err := FromStatic(pki.RootCertPEM)
|
||||
ta, err := From(Options{Anchors: pki.RootCertPEM})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
|
@ -176,10 +176,10 @@ func TestStatic_Watch(t *testing.T) {
|
|||
|
||||
t.Run("should return when cancel is closed via closed Run", func(t *testing.T) {
|
||||
pki := test.GenPKI(t, test.PKIOptions{})
|
||||
ta, err := FromStatic(pki.RootCertPEM)
|
||||
ta, err := From(Options{Anchors: pki.RootCertPEM})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
doneCh := make(chan struct{})
|
||||
errCh := make(chan error)
|
||||
|
||||
|
@ -188,7 +188,7 @@ func TestStatic_Watch(t *testing.T) {
|
|||
}()
|
||||
|
||||
go func() {
|
||||
ta.Watch(context.Background(), nil)
|
||||
ta.Watch(t.Context(), nil)
|
||||
close(doneCh)
|
||||
}()
|
||||
|
|
@ -16,6 +16,7 @@ package trustanchors
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/jwtbundle"
|
||||
"github.com/spiffe/go-spiffe/v2/bundle/x509bundle"
|
||||
)
|
||||
|
||||
|
@ -23,8 +24,10 @@ import (
|
|||
// Allows consumers to get the current trust anchor bundle, and subscribe to
|
||||
// bundle updates.
|
||||
type Interface interface {
|
||||
// Source implements the SPIFFE trust anchor bundle source.
|
||||
// Source implements the SPIFFE trust anchor x509 bundle source.
|
||||
x509bundle.Source
|
||||
// Source implements the SPIFFE trust anchor jwt bundle source.
|
||||
jwtbundle.Source
|
||||
|
||||
// CurrentTrustAnchors returns the current trust anchor PEM bundle.
|
||||
CurrentTrustAnchors(ctx context.Context) ([]byte, error)
|
||||
|
|
|
@ -155,12 +155,12 @@ func (p PKI) ClientGRPCCtx(t *testing.T) context.Context {
|
|||
server.Serve(lis)
|
||||
}()
|
||||
//nolint:staticcheck
|
||||
conn, err := grpc.DialContext(context.Background(), lis.Addr().String(),
|
||||
conn, err := grpc.DialContext(t.Context(), lis.Addr().String(),
|
||||
grpc.WithTransportCredentials(grpccredentials.MTLSClientCredentials(clientSVID, clientSVID, tlsconfig.AuthorizeAny())),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = helloworld.NewGreeterClient(conn).SayHello(context.Background(), new(helloworld.HelloRequest))
|
||||
_, err = helloworld.NewGreeterClient(conn).SayHello(t.Context(), new(helloworld.HelloRequest))
|
||||
require.NoError(t, err)
|
||||
|
||||
lis.Close()
|
||||
|
|
|
@ -41,7 +41,7 @@ pHZ3vWGFAoGAc5Um3YYkhh2QScQBy5+kumH40LhFFy2ETznWEp0tS2NwmTfTm/Nl
|
|||
Sg+Ct2nOw93cIhwDjWyoilkIapuuX2obY+sUc3kj2ugU+hONfuBStsF020IPP1sk
|
||||
A9okIZVbz8ycqcjaBiNc4+TeiXED1K7bV9Kg+A9lxDxfGRybJ1/ECWA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
` // #nosec G101
|
||||
privateKeyRSAPKCS8 = `-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcjaZ0griZFG77
|
||||
LAytiNRnMHG3Q2UBUusyEaVomxvLs9ZMyIullWKnhIEP0bCcJTRMYUPuTb7u1+zT
|
||||
|
@ -128,7 +128,7 @@ MHcCAQEEIOcFe4Q6ardS97ml2tV4+194nmlfQPh8o9ir/qsacEozoAoGCCqGSM49
|
|||
AwEHoUQDQgAEUMn1c2ioMNi2DqvC8hdBVUERFZ97eVFsNVcQIgR0Hsq5PVrQ/dQ4
|
||||
uI5u97b6k4wXHYFXMvPmsW1T6qZAE9bB3Q==
|
||||
-----END EC PRIVATE KEY-----
|
||||
`
|
||||
` // #nosec G101
|
||||
privateKeyP256PKCS8 = `-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5wV7hDpqt1L3uaXa
|
||||
1Xj7X3ieaV9A+Hyj2Kv+qxpwSjOhRANCAARQyfVzaKgw2LYOq8LyF0FVQREVn3t5
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2024 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetDurationWithRange returns the time.Duration value of the environment variable specified by `envVar`.
|
||||
// If the environment variable is not set, it returns `defaultValue`.
|
||||
// If the value is set but is not valid (not a valid time.Duration or falls outside the specified range
|
||||
// [minValue, maxValue] inclusively), it returns `defaultValue` and an error.
|
||||
func GetDurationWithRange(envVar string, defaultValue, min, max time.Duration) (time.Duration, error) {
|
||||
v := os.Getenv(envVar)
|
||||
if v == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
val, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return defaultValue, fmt.Errorf("invalid time.Duration value %s for the %s env variable: %w", val, envVar, err)
|
||||
}
|
||||
|
||||
if val < min || val > max {
|
||||
return defaultValue, fmt.Errorf("invalid value for the %s env variable: value should be between %s and %s, got %s", envVar, min, max, val)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
|
@ -1,4 +1,17 @@
|
|||
package utils
|
||||
/*
|
||||
Copyright 2024 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -7,7 +20,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetEnvIntWithRangeWrongValues(t *testing.T) {
|
||||
func TestGetIntWithRangeWrongValues(t *testing.T) {
|
||||
testValues := []struct {
|
||||
name string
|
||||
envVarVal string
|
||||
|
@ -43,7 +56,7 @@ func TestGetEnvIntWithRangeWrongValues(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("MY_ENV", tt.envVarVal)
|
||||
|
||||
val, err := GetEnvDurationWithRange("MY_ENV", defaultValue, tt.min, tt.max)
|
||||
val, err := GetDurationWithRange("MY_ENV", defaultValue, tt.min, tt.max)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.error)
|
||||
require.Equal(t, defaultValue, val)
|
||||
|
@ -75,7 +88,7 @@ func TestGetEnvDurationWithRangeValidValues(t *testing.T) {
|
|||
t.Setenv("MY_ENV", tt.envVarVal)
|
||||
}
|
||||
|
||||
val, err := GetEnvDurationWithRange("MY_ENV", 3*time.Second, time.Second, 5*time.Second)
|
||||
val, err := GetDurationWithRange("MY_ENV", 3*time.Second, time.Second, 5*time.Second)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.result, val)
|
||||
})
|
|
@ -24,7 +24,7 @@ const (
|
|||
CodePrefixStateStore = "DAPR_STATE_"
|
||||
CodePrefixPubSub = "DAPR_PUBSUB_"
|
||||
CodePrefixBindings = "DAPR_BINDING_"
|
||||
CodePrefixSecretStore = "DAPR_SECRET_"
|
||||
CodePrefixSecretStore = "DAPR_SECRET_" // #nosec G101
|
||||
CodePrefixConfigurationStore = "DAPR_CONFIGURATION_"
|
||||
CodePrefixLock = "DAPR_LOCK_"
|
||||
CodePrefixNameResolution = "DAPR_NAME_RESOLUTION_"
|
||||
|
|
|
@ -63,7 +63,7 @@ type Error struct {
|
|||
|
||||
// ErrorBuilder is used to build the error
|
||||
type ErrorBuilder struct {
|
||||
err Error
|
||||
err *Error
|
||||
}
|
||||
|
||||
// errorJSON is used to build the error for the HTTP Methods json output
|
||||
|
@ -106,12 +106,12 @@ func (e *Error) Category() string {
|
|||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e Error) Error() string {
|
||||
func (e *Error) Error() string {
|
||||
return e.String()
|
||||
}
|
||||
|
||||
// String returns the string representation.
|
||||
func (e Error) String() string {
|
||||
func (e *Error) String() string {
|
||||
return fmt.Sprintf(errStringFormat, e.grpcCode.String(), e.message)
|
||||
}
|
||||
|
||||
|
@ -140,9 +140,9 @@ func FromError(err error) (*Error, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
var kitErr Error
|
||||
var kitErr *Error
|
||||
if errors.As(err, &kitErr) {
|
||||
return &kitErr, true
|
||||
return kitErr, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
|
@ -151,7 +151,7 @@ func FromError(err error) (*Error, bool) {
|
|||
/*** GRPC Methods ***/
|
||||
|
||||
// GRPCStatus returns the gRPC status.Status object.
|
||||
func (e Error) GRPCStatus() *status.Status {
|
||||
func (e *Error) GRPCStatus() *status.Status {
|
||||
stat := status.New(e.grpcCode, e.message)
|
||||
|
||||
// convert details from proto.Msg -> protoiface.MsgV1
|
||||
|
@ -178,7 +178,7 @@ func (e Error) GRPCStatus() *status.Status {
|
|||
/*** HTTP Methods ***/
|
||||
|
||||
// JSONErrorValue implements the errorResponseValue interface.
|
||||
func (e Error) JSONErrorValue() []byte {
|
||||
func (e *Error) JSONErrorValue() []byte {
|
||||
grpcStatus := e.GRPCStatus().Proto()
|
||||
|
||||
// Make httpCode human readable
|
||||
|
@ -200,7 +200,7 @@ func (e Error) JSONErrorValue() []byte {
|
|||
if len(details) > 0 {
|
||||
errJSON.Details = make([]any, len(details))
|
||||
for i, detail := range details {
|
||||
detailMap, errorCode := convertErrorDetails(detail, e)
|
||||
detailMap, errorCode := convertErrorDetails(detail, *e)
|
||||
errJSON.Details[i] = detailMap
|
||||
|
||||
// If there is an errorCode, update the overall ErrorCode
|
||||
|
@ -357,7 +357,7 @@ ErrorBuilder
|
|||
// NewBuilder create a new ErrorBuilder using the supplied required error fields
|
||||
func NewBuilder(grpcCode grpcCodes.Code, httpCode int, message string, tag string, category string) *ErrorBuilder {
|
||||
return &ErrorBuilder{
|
||||
err: Error{
|
||||
err: &Error{
|
||||
details: make([]proto.Message, 0),
|
||||
grpcCode: grpcCode,
|
||||
httpCode: httpCode,
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestError_HTTPStatusCode(t *testing.T) {
|
|||
WithErrorInfo("fake", map[string]string{"fake": "test"}).
|
||||
Build()
|
||||
|
||||
err, ok := kitErr.(Error)
|
||||
err, ok := kitErr.(*Error)
|
||||
require.True(t, ok, httpStatusCode, err.HTTPStatusCode())
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ func TestError_GrpcStatusCode(t *testing.T) {
|
|||
WithErrorInfo("fake", map[string]string{"fake": "test"}).
|
||||
Build()
|
||||
|
||||
err, ok := kitErr.(Error)
|
||||
err, ok := kitErr.(*Error)
|
||||
require.True(t, ok, grpcStatusCode, err.GrpcStatusCode())
|
||||
}
|
||||
|
||||
|
@ -171,7 +171,7 @@ func TestError_Error(t *testing.T) {
|
|||
t.Errorf("got = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
err, ok := kitErr.(Error)
|
||||
err, ok := kitErr.(*Error)
|
||||
require.True(t, ok, err.Is(kitErr))
|
||||
})
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ func TestErrorBuilder_WithErrorInfo(t *testing.T) {
|
|||
Metadata: metadata,
|
||||
}
|
||||
|
||||
expected := Error{
|
||||
expected := &Error{
|
||||
grpcCode: grpcCodes.ResourceExhausted,
|
||||
httpCode: http.StatusTeapot,
|
||||
message: "fake_message",
|
||||
|
@ -240,7 +240,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) {
|
|||
name string
|
||||
fields fields
|
||||
args args
|
||||
want Error
|
||||
want *Error
|
||||
}{
|
||||
{
|
||||
name: "Has_Multiple_Details",
|
||||
|
@ -263,7 +263,7 @@ func TestErrorBuilder_WithDetails(t *testing.T) {
|
|||
Description: "test_description",
|
||||
},
|
||||
}},
|
||||
want: Error{
|
||||
want: &Error{
|
||||
grpcCode: grpcCodes.ResourceExhausted,
|
||||
httpCode: http.StatusTeapot,
|
||||
message: "fake_message",
|
||||
|
@ -802,7 +802,7 @@ func TestErrorBuilder_Build(t *testing.T) {
|
|||
"some_category",
|
||||
).WithErrorInfo("fake", map[string]string{"fake": "test"}).Build()
|
||||
|
||||
builtErr, ok := built.(Error)
|
||||
builtErr, ok := built.(*Error)
|
||||
require.True(t, ok)
|
||||
|
||||
containsErrorInfo := false
|
||||
|
@ -828,7 +828,7 @@ func TestErrorBuilder_Build(t *testing.T) {
|
|||
"some_category",
|
||||
).WithErrorInfo("SOME_ERROR", map[string]string{"fake": "test"}).Build()
|
||||
|
||||
builtErr, ok := built.(Error)
|
||||
builtErr, ok := built.(*Error)
|
||||
require.True(t, ok)
|
||||
|
||||
containsErrorInfo := false
|
||||
|
@ -990,7 +990,7 @@ func TestFromError(t *testing.T) {
|
|||
t.Errorf("Expected result to be nil and ok to be false, got result: %v, ok: %t", result, ok)
|
||||
}
|
||||
|
||||
kitErr := Error{
|
||||
kitErr := &Error{
|
||||
grpcCode: grpcCodes.ResourceExhausted,
|
||||
httpCode: http.StatusTeapot,
|
||||
message: "fake_message",
|
||||
|
@ -999,8 +999,8 @@ func TestFromError(t *testing.T) {
|
|||
}
|
||||
|
||||
result, ok = FromError(kitErr)
|
||||
if !ok || !reflect.DeepEqual(result, &kitErr) {
|
||||
t.Errorf("Expected result to be %#v and ok to be true, got result: %#v, ok: %t", &kitErr, result, ok)
|
||||
if !ok || !reflect.DeepEqual(result, kitErr) {
|
||||
t.Errorf("Expected result to be %#v and ok to be true, got result: %#v, ok: %t", kitErr, result, ok)
|
||||
}
|
||||
|
||||
var nonKitError error
|
||||
|
@ -1011,7 +1011,7 @@ func TestFromError(t *testing.T) {
|
|||
|
||||
wrapped := fmt.Errorf("wrapped: %w", kitErr)
|
||||
result, ok = FromError(wrapped)
|
||||
if !ok || !reflect.DeepEqual(result, &kitErr) {
|
||||
t.Errorf("Expected result to be %#v and ok to be true, got result: %#v, ok: %t", &kitErr, result, ok)
|
||||
if !ok || !reflect.DeepEqual(result, kitErr) {
|
||||
t.Errorf("Expected result to be %#v and ok to be true, got result: %#v, ok: %t", kitErr, result, ok)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ limitations under the License.
|
|||
package batcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -44,7 +43,7 @@ func TestSubscribe(t *testing.T) {
|
|||
|
||||
b := New[string, struct{}](Options{Interval: time.Millisecond * 10})
|
||||
ch := make(chan struct{})
|
||||
b.Subscribe(context.Background(), ch)
|
||||
b.Subscribe(t.Context(), ch)
|
||||
assert.Len(t, b.eventChs, 1)
|
||||
}
|
||||
|
||||
|
@ -59,8 +58,8 @@ func TestBatch(t *testing.T) {
|
|||
ch1 := make(chan struct{})
|
||||
ch2 := make(chan struct{})
|
||||
ch3 := make(chan struct{})
|
||||
b.Subscribe(context.Background(), ch1, ch2)
|
||||
b.Subscribe(context.Background(), ch3)
|
||||
b.Subscribe(t.Context(), ch1, ch2)
|
||||
b.Subscribe(t.Context(), ch3)
|
||||
|
||||
b.Batch("key1", struct{}{})
|
||||
b.Batch("key1", struct{}{})
|
||||
|
@ -95,7 +94,7 @@ func TestBatch(t *testing.T) {
|
|||
|
||||
fakeClock.Step(time.Millisecond * 5)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
for _, ch := range []chan struct{}{ch1, ch2, ch3} {
|
||||
select {
|
||||
case <-ch:
|
||||
|
@ -115,10 +114,10 @@ func TestBatch(t *testing.T) {
|
|||
ch1 := make(chan int, 10)
|
||||
ch2 := make(chan int, 10)
|
||||
ch3 := make(chan int, 10)
|
||||
b.Subscribe(context.Background(), ch1, ch2)
|
||||
b.Subscribe(context.Background(), ch3)
|
||||
b.Subscribe(t.Context(), ch1, ch2)
|
||||
b.Subscribe(t.Context(), ch3)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
b.Batch(i, i)
|
||||
b.Batch(i, i+1)
|
||||
b.Batch(i, i+2)
|
||||
|
@ -126,7 +125,7 @@ func TestBatch(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, ch := range []chan int{ch1} {
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := range 10 {
|
||||
select {
|
||||
case v := <-ch:
|
||||
assert.Equal(t, i+2, v)
|
||||
|
@ -143,7 +142,7 @@ func TestClose(t *testing.T) {
|
|||
|
||||
b := New[string, struct{}](Options{Interval: time.Millisecond * 10})
|
||||
ch := make(chan struct{})
|
||||
b.Subscribe(context.Background(), ch)
|
||||
b.Subscribe(t.Context(), ch)
|
||||
assert.Len(t, b.eventChs, 1)
|
||||
b.Batch("key1", struct{}{})
|
||||
b.Close()
|
||||
|
@ -156,6 +155,6 @@ func TestSubscribeAfterClose(t *testing.T) {
|
|||
b := New[string, struct{}](Options{Interval: time.Millisecond * 10})
|
||||
b.Close()
|
||||
ch := make(chan struct{})
|
||||
b.Subscribe(context.Background(), ch)
|
||||
b.Subscribe(t.Context(), ch)
|
||||
assert.Empty(t, b.eventChs)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dapr/kit/events/loop"
|
||||
)
|
||||
|
||||
type Fake[T any] struct {
|
||||
runFn func(context.Context) error
|
||||
enqueueFn func(T)
|
||||
closeFn func(T)
|
||||
}
|
||||
|
||||
func New[T any]() *Fake[T] {
|
||||
return &Fake[T]{
|
||||
runFn: func(context.Context) error { return nil },
|
||||
enqueueFn: func(T) {},
|
||||
closeFn: func(T) {},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fake[T]) WithRun(fn func(context.Context) error) *Fake[T] {
|
||||
f.runFn = fn
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Fake[T]) WithEnqueue(fn func(T)) *Fake[T] {
|
||||
f.enqueueFn = fn
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Fake[T]) WithClose(fn func(T)) *Fake[T] {
|
||||
f.closeFn = fn
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *Fake[T]) Run(ctx context.Context) error {
|
||||
return f.runFn(ctx)
|
||||
}
|
||||
|
||||
func (f *Fake[T]) Enqueue(t T) {
|
||||
f.enqueueFn(t)
|
||||
}
|
||||
|
||||
func (f *Fake[T]) Close(t T) {
|
||||
f.closeFn(t)
|
||||
}
|
||||
|
||||
func (f *Fake[T]) Reset(loop.Handler[T], uint64) loop.Interface[T] {
|
||||
return f
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2025 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dapr/kit/events/loop"
|
||||
)
|
||||
|
||||
func Test_Fake(*testing.T) {
|
||||
var _ loop.Interface[int] = New[int]()
|
||||
}
|
|
@ -15,58 +15,97 @@ package loop
|
|||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type HandlerFunc[T any] func(context.Context, T) error
|
||||
|
||||
type Options[T any] struct {
|
||||
Handler HandlerFunc[T]
|
||||
BufferSize *uint64
|
||||
type Handler[T any] interface {
|
||||
Handle(ctx context.Context, t T) error
|
||||
}
|
||||
|
||||
type Loop[T any] struct {
|
||||
type Interface[T any] interface {
|
||||
Run(ctx context.Context) error
|
||||
Enqueue(t T)
|
||||
Close(t T)
|
||||
Reset(h Handler[T], size uint64) Interface[T]
|
||||
}
|
||||
|
||||
type loop[T any] struct {
|
||||
queue chan T
|
||||
handler HandlerFunc[T]
|
||||
handler Handler[T]
|
||||
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func New[T any](opts Options[T]) *Loop[T] {
|
||||
size := 1
|
||||
if opts.BufferSize != nil {
|
||||
size = int(*opts.BufferSize)
|
||||
}
|
||||
|
||||
return &Loop[T]{
|
||||
func New[T any](h Handler[T], size uint64) Interface[T] {
|
||||
return &loop[T]{
|
||||
queue: make(chan T, size),
|
||||
handler: h,
|
||||
closeCh: make(chan struct{}),
|
||||
handler: opts.Handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop[T]) Run(ctx context.Context) error {
|
||||
func Empty[T any]() Interface[T] {
|
||||
return new(loop[T])
|
||||
}
|
||||
|
||||
func (l *loop[T]) Run(ctx context.Context) error {
|
||||
defer close(l.closeCh)
|
||||
|
||||
for {
|
||||
var req T
|
||||
select {
|
||||
case req = <-l.queue:
|
||||
case <-ctx.Done():
|
||||
req, ok := <-l.queue
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := l.handler(ctx, req); err != nil {
|
||||
if err := l.handler.Handle(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop[T]) Enqueue(req T) {
|
||||
func (l *loop[T]) Enqueue(req T) {
|
||||
l.lock.RLock()
|
||||
defer l.lock.RUnlock()
|
||||
|
||||
if l.closed {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case l.queue <- req:
|
||||
case <-l.closeCh:
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loop[T]) Close(req T) {
|
||||
l.lock.Lock()
|
||||
l.closed = true
|
||||
select {
|
||||
case l.queue <- req:
|
||||
case <-l.closeCh:
|
||||
}
|
||||
close(l.queue)
|
||||
l.lock.Unlock()
|
||||
<-l.closeCh
|
||||
}
|
||||
|
||||
func (l *loop[T]) Reset(h Handler[T], size uint64) Interface[T] {
|
||||
if l == nil {
|
||||
return New[T](h, size)
|
||||
}
|
||||
|
||||
l.lock.Lock()
|
||||
defer l.lock.Unlock()
|
||||
|
||||
l.closed = false
|
||||
l.closeCh = make(chan struct{})
|
||||
l.handler = h
|
||||
|
||||
// TODO: @joshvanl: use a ring buffer so that we don't need to reallocate and
|
||||
// improve performance.
|
||||
l.queue = make(chan T, size)
|
||||
|
||||
return l
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func ExampleProcessor() {
|
|||
// Using Dequeue allows removing an item from the queue
|
||||
processor.Dequeue("item4")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
fmt.Println(<-executed)
|
||||
}
|
||||
// Output:
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestProcessor(t *testing.T) {
|
|||
)
|
||||
now := clock.Now()
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
@ -312,7 +312,7 @@ func TestProcessor(t *testing.T) {
|
|||
close(doneCh)
|
||||
|
||||
// Ensure all items are true
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
assert.Truef(t, collected[i], "item %d not received", i)
|
||||
}
|
||||
})
|
||||
|
@ -402,7 +402,7 @@ func TestClose(t *testing.T) {
|
|||
default:
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
for range 3 {
|
||||
select {
|
||||
case err := <-closeCh:
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestCoalescing(t *testing.T) {
|
|||
ch := make(chan struct{})
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- c.Run(context.Background(), ch)
|
||||
errCh <- c.Run(t.Context(), ch)
|
||||
}()
|
||||
|
||||
t.Cleanup(func() {
|
||||
|
@ -78,7 +78,7 @@ func TestCoalescing(t *testing.T) {
|
|||
c, err := NewCoalescing(OptionsCoalescing{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- c.Run(ctx, make(chan struct{}))
|
||||
|
@ -100,7 +100,7 @@ func TestCoalescing(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- c.Run(context.Background(), make(chan struct{}))
|
||||
errCh <- c.Run(t.Context(), make(chan struct{}))
|
||||
}()
|
||||
|
||||
c.Close()
|
||||
|
@ -119,7 +119,7 @@ func TestCoalescing(t *testing.T) {
|
|||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
errCh <- c.Run(context.Background(), make(chan struct{}))
|
||||
errCh <- c.Run(t.Context(), make(chan struct{}))
|
||||
}()
|
||||
|
||||
c.Close()
|
||||
|
@ -132,7 +132,7 @@ func TestCoalescing(t *testing.T) {
|
|||
}
|
||||
|
||||
go func() {
|
||||
errCh <- c.Run(context.Background(), make(chan struct{}))
|
||||
errCh <- c.Run(t.Context(), make(chan struct{}))
|
||||
}()
|
||||
|
||||
select {
|
||||
|
@ -277,7 +277,7 @@ func TestCoalescing(t *testing.T) {
|
|||
c.Add()
|
||||
assertNoChannel(t, ch)
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond)
|
||||
clock.Step(time.Second * 4)
|
||||
c.Add()
|
||||
|
@ -345,7 +345,7 @@ func TestCoalescing(t *testing.T) {
|
|||
assertChannel(t, ch)
|
||||
|
||||
assert.Eventually(t, c.hasTimer.Load, time.Second, time.Millisecond)
|
||||
for i := 0; i < 10; i++ {
|
||||
for range 10 {
|
||||
c.Add()
|
||||
}
|
||||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond)
|
||||
|
|
|
@ -43,7 +43,7 @@ func TestFSWatcher(t *testing.T) {
|
|||
}
|
||||
|
||||
errCh := make(chan error)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
eventsCh := make(chan struct{})
|
||||
go func() {
|
||||
errCh <- f.Run(ctx, eventsCh)
|
||||
|
@ -84,7 +84,7 @@ func TestFSWatcher(t *testing.T) {
|
|||
t.Run("running Run twice should error", func(t *testing.T) {
|
||||
fs, err := New(Options{})
|
||||
require.NoError(t, err)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
require.NoError(t, fs.Run(ctx, make(chan struct{})))
|
||||
require.Error(t, fs.Run(ctx, make(chan struct{})))
|
||||
|
@ -101,7 +101,7 @@ func TestFSWatcher(t *testing.T) {
|
|||
|
||||
t.Run("should fire event when event occurs on target file", func(t *testing.T) {
|
||||
fp := filepath.Join(t.TempDir(), "test.txt")
|
||||
require.NoError(t, os.WriteFile(fp, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp, []byte{}, 0o600))
|
||||
eventsCh := runWatcher(t, Options{
|
||||
Targets: []string{fp},
|
||||
Interval: ptr.Of(time.Duration(1)),
|
||||
|
@ -112,7 +112,7 @@ func TestFSWatcher(t *testing.T) {
|
|||
// If running in windows, wait for notify to be ready.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
require.NoError(t, os.WriteFile(fp, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp, []byte{}, 0o600))
|
||||
|
||||
select {
|
||||
case <-eventsCh:
|
||||
|
@ -124,16 +124,16 @@ func TestFSWatcher(t *testing.T) {
|
|||
t.Run("should fire 2 events when event occurs on 2 file target", func(t *testing.T) {
|
||||
fp1 := filepath.Join(t.TempDir(), "test.txt")
|
||||
fp2 := filepath.Join(t.TempDir(), "test.txt")
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
eventsCh := runWatcher(t, Options{
|
||||
Targets: []string{fp1, fp2},
|
||||
Interval: ptr.Of(time.Duration(1)),
|
||||
}, nil)
|
||||
assert.Empty(t, eventsCh)
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
for i := 0; i < 2; i++ {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
for range 2 {
|
||||
select {
|
||||
case <-eventsCh:
|
||||
case <-time.After(time.Second):
|
||||
|
@ -146,8 +146,8 @@ func TestFSWatcher(t *testing.T) {
|
|||
dir := t.TempDir()
|
||||
fp1 := filepath.Join(dir, "test1.txt")
|
||||
fp2 := filepath.Join(dir, "test2.txt")
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
eventsCh := runWatcher(t, Options{
|
||||
Targets: []string{fp1, fp2},
|
||||
Interval: ptr.Of(time.Duration(1)),
|
||||
|
@ -157,9 +157,9 @@ func TestFSWatcher(t *testing.T) {
|
|||
time.Sleep(time.Second)
|
||||
}
|
||||
assert.Empty(t, eventsCh)
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
for i := 0; i < 2; i++ {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
for range 2 {
|
||||
select {
|
||||
case <-eventsCh:
|
||||
case <-time.After(time.Second):
|
||||
|
@ -178,9 +178,9 @@ func TestFSWatcher(t *testing.T) {
|
|||
Interval: ptr.Of(time.Duration(1)),
|
||||
}, nil)
|
||||
assert.Empty(t, eventsCh)
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
for i := 0; i < 2; i++ {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
for range 2 {
|
||||
select {
|
||||
case <-eventsCh:
|
||||
case <-time.After(time.Second):
|
||||
|
@ -207,9 +207,9 @@ func TestFSWatcher(t *testing.T) {
|
|||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
for range 10 {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
}
|
||||
|
||||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond*10)
|
||||
|
@ -222,9 +222,9 @@ func TestFSWatcher(t *testing.T) {
|
|||
|
||||
clock.Step(time.Millisecond * 250)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o644))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o644))
|
||||
for range 10 {
|
||||
require.NoError(t, os.WriteFile(fp1, []byte{}, 0o600))
|
||||
require.NoError(t, os.WriteFile(fp2, []byte{}, 0o600))
|
||||
}
|
||||
|
||||
select {
|
||||
|
@ -236,7 +236,7 @@ func TestFSWatcher(t *testing.T) {
|
|||
assert.Eventually(t, clock.HasWaiters, time.Second, time.Millisecond*10)
|
||||
clock.Step(time.Millisecond * 500)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
for range 2 {
|
||||
select {
|
||||
case <-eventsCh:
|
||||
case <-time.After(time.Second):
|
||||
|
|
29
go.mod
29
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/dapr/kit
|
||||
|
||||
go 1.23.1
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/alphadose/haxmap v1.3.1
|
||||
|
@ -11,16 +11,16 @@ require (
|
|||
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cast v1.5.1
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.7
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237
|
||||
google.golang.org/grpc v1.64.0
|
||||
golang.org/x/tools v0.33.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
k8s.io/apimachinery v0.26.9
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
|
||||
)
|
||||
|
@ -28,6 +28,7 @@ require (
|
|||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
|
@ -36,12 +37,12 @@ require (
|
|||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/zeebo/errs v1.3.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
84
go.sum
84
go.sum
|
@ -1,5 +1,5 @@
|
|||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/alphadose/haxmap v1.3.1 h1:KmZh75duO1tC8pt3LmUwoTYiZ9sh4K52FX8p7/yrlqU=
|
||||
github.com/alphadose/haxmap v1.3.1/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
|
@ -13,16 +13,24 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
|
|||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
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/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
|
||||
github.com/google/gofuzz v1.1.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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
@ -57,70 +65,82 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
|||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.7 h1:VUkM1yIyg/x8X7u1uXqSRVRCdMdfRIEdFBzpqoeASGk=
|
||||
github.com/spiffe/go-spiffe/v2 v2.1.7/go.mod h1:QJDGdhXllxjxvd5B+2XnhhXB/+rC8gr+lNrtOryiWeE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0=
|
||||
github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
|
||||
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI=
|
||||
google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -37,9 +37,9 @@ import (
|
|||
"github.com/lestrrat-go/httprc"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
|
||||
"github.com/dapr/kit/crypto/pem"
|
||||
"github.com/dapr/kit/fswatcher"
|
||||
"github.com/dapr/kit/logger"
|
||||
"github.com/dapr/kit/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -198,7 +198,7 @@ func (c *JWKSCache) initJWKSFromURL(ctx context.Context, url string) error {
|
|||
|
||||
// Load CA certificates if we have one
|
||||
if c.caCertificate != "" {
|
||||
caCert, err := utils.GetPEM(c.caCertificate)
|
||||
caCert, err := pem.GetPEM(c.caCertificate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load CA certificate: %w", err)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
|
||||
t.Run("init with value", func(t *testing.T) {
|
||||
cache := NewJWKSCache(testJWKS1, log)
|
||||
err := cache.initCache(context.Background())
|
||||
err := cache.initCache(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
set := cache.KeySet()
|
||||
|
@ -53,7 +53,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
|
||||
t.Run("init with base64-encoded value", func(t *testing.T) {
|
||||
cache := NewJWKSCache(base64.StdEncoding.EncodeToString([]byte(testJWKS1)), log)
|
||||
err := cache.initCache(context.Background())
|
||||
err := cache.initCache(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
set := cache.KeySet()
|
||||
|
@ -68,12 +68,12 @@ func TestJWKSCache(t *testing.T) {
|
|||
// Create a temporary directory and put the JWKS in there
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "jwks.json")
|
||||
err := os.WriteFile(path, []byte(testJWKS1), 0o666)
|
||||
err := os.WriteFile(path, []byte(testJWKS1), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should wait for first file to be loaded before initialization is reported as completed
|
||||
cache := NewJWKSCache(path, log)
|
||||
err = cache.initCache(context.Background())
|
||||
err = cache.initCache(t.Context())
|
||||
require.NoError(t, err)
|
||||
|
||||
set := cache.KeySet()
|
||||
|
@ -87,7 +87,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
time.Sleep(time.Second)
|
||||
|
||||
// Update the file and verify it's picked up
|
||||
err = os.WriteFile(path, []byte(testJWKS2), 0o666)
|
||||
err = os.WriteFile(path, []byte(testJWKS2), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
|
@ -127,7 +127,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
cache := NewJWKSCache("http://localhost/jwks.json", log)
|
||||
cache.SetHTTPClient(client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
err := cache.initCache(ctx)
|
||||
require.NoError(t, err)
|
||||
|
@ -142,7 +142,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
|
||||
t.Run("start and wait for init", func(t *testing.T) {
|
||||
cache := NewJWKSCache(testJWKS1, log)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Start in background
|
||||
|
@ -174,7 +174,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
cache := NewJWKSCache("https://localhost/jwks.json", log)
|
||||
cache.SetHTTPClient(client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Start in background
|
||||
|
@ -194,7 +194,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("start and init times out", func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 1500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Create a custom HTTP client with a RoundTripper that doesn't require starting a TCP listener
|
||||
|
@ -223,7 +223,7 @@ func TestJWKSCache(t *testing.T) {
|
|||
}()
|
||||
|
||||
// Wait for initialization
|
||||
err := cache.WaitForCacheReady(context.Background())
|
||||
err := cache.WaitForCacheReady(t.Context())
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "failed to fetch JWKS")
|
||||
require.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
const fakeLoggerName = "fakeLogger"
|
||||
|
@ -286,7 +285,7 @@ func TestWithTypeFields(t *testing.T) {
|
|||
testLogger.Info("testLogger with log LogType")
|
||||
|
||||
b, _ = buf.ReadBytes('\n')
|
||||
maps.Clear(o)
|
||||
clear(o)
|
||||
require.NoError(t, json.Unmarshal(b, &o))
|
||||
|
||||
assert.Equalf(t, LogTypeLog, o[logFieldType], "testLogger must be %s type", LogTypeLog)
|
||||
|
@ -309,12 +308,12 @@ func TestWithFields(t *testing.T) {
|
|||
}).Info("🙃")
|
||||
|
||||
b, _ := buf.ReadBytes('\n')
|
||||
maps.Clear(o)
|
||||
clear(o)
|
||||
require.NoError(t, json.Unmarshal(b, &o))
|
||||
|
||||
assert.Equal(t, "🙃", o["msg"])
|
||||
assert.Equal(t, "world", o["hello"])
|
||||
assert.Equal(t, float64(42), o["answer"])
|
||||
assert.InDelta(t, float64(42), o["answer"], 000.1)
|
||||
|
||||
// Test with other fields
|
||||
testLogger.WithFields(map[string]any{
|
||||
|
@ -322,7 +321,7 @@ func TestWithFields(t *testing.T) {
|
|||
}).Info("🐶")
|
||||
|
||||
b, _ = buf.ReadBytes('\n')
|
||||
maps.Clear(o)
|
||||
clear(o)
|
||||
require.NoError(t, json.Unmarshal(b, &o))
|
||||
|
||||
assert.Equal(t, "🐶", o["msg"])
|
||||
|
@ -336,7 +335,7 @@ func TestWithFields(t *testing.T) {
|
|||
testLogger.Info("🤔")
|
||||
|
||||
b, _ = buf.ReadBytes('\n')
|
||||
maps.Clear(o)
|
||||
clear(o)
|
||||
require.NoError(t, json.Unmarshal(b, &o))
|
||||
|
||||
assert.Equal(t, "🤔", o["msg"])
|
||||
|
|
|
@ -14,7 +14,6 @@ limitations under the License.
|
|||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -78,7 +77,7 @@ func TestToLogLevel(t *testing.T) {
|
|||
|
||||
func TestNewContext(t *testing.T) {
|
||||
t.Run("input nil logger", func(t *testing.T) {
|
||||
ctx := NewContext(context.Background(), nil)
|
||||
ctx := NewContext(t.Context(), nil)
|
||||
assert.NotNil(t, ctx, "ctx is not nil")
|
||||
|
||||
logger := FromContextOrDefault(ctx)
|
||||
|
@ -91,7 +90,7 @@ func TestNewContext(t *testing.T) {
|
|||
logger := NewLogger(testLoggerName)
|
||||
assert.NotNil(t, logger)
|
||||
|
||||
ctx := NewContext(context.Background(), logger)
|
||||
ctx := NewContext(t.Context(), logger)
|
||||
assert.NotNil(t, ctx, "ctx is not nil")
|
||||
logger2 := FromContextOrDefault(ctx)
|
||||
assert.NotNil(t, logger2)
|
||||
|
|
|
@ -29,7 +29,7 @@ type Duration struct {
|
|||
time.Duration
|
||||
}
|
||||
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
func (d *Duration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(d.String())
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ func toTimeDurationHookFunc() mapstructure.DecodeHookFunc {
|
|||
// This methods supports days, hours, minutes, and seconds. It assumes all durations are in UTC time and are not impacted by DST (so all days are 24-hours long).
|
||||
// This method does not support fractions of seconds, and durations are truncated to seconds.
|
||||
// See https://en.wikipedia.org/wiki/ISO_8601#Durations for referece.
|
||||
func (d Duration) ToISOString() string {
|
||||
func (d *Duration) ToISOString() string {
|
||||
// Truncate to seconds, removing fractional seconds
|
||||
trunc := d.Truncate(time.Second)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/dapr/kit/ptr"
|
||||
"github.com/dapr/kit/utils"
|
||||
kitstrings "github.com/dapr/kit/strings"
|
||||
)
|
||||
|
||||
func toTruthyBoolHookFunc() mapstructure.DecodeHookFunc {
|
||||
|
@ -37,10 +37,10 @@ func toTruthyBoolHookFunc() mapstructure.DecodeHookFunc {
|
|||
data any,
|
||||
) (any, error) {
|
||||
if f == stringType && t == boolType {
|
||||
return utils.IsTruthy(data.(string)), nil
|
||||
return kitstrings.IsTruthy(data.(string)), nil
|
||||
}
|
||||
if f == stringType && t == boolPtrType {
|
||||
return ptr.Of(utils.IsTruthy(data.(string))), nil
|
||||
return ptr.Of(kitstrings.IsTruthy(data.(string))), nil
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ func resolveAliases(md map[string]string, t reflect.Type) error {
|
|||
|
||||
func resolveAliasesInType(md map[string]string, keys map[string]string, t reflect.Type) {
|
||||
// Iterate through all the properties of the type to see if anyone has the "mapstructurealiases" property
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
for i := range t.NumField() {
|
||||
currentField := t.Field(i)
|
||||
|
||||
// Ignored fields that are not exported or that don't have a "mapstructure" tag
|
||||
|
|
|
@ -14,13 +14,13 @@ limitations under the License.
|
|||
package metadata
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func TestMetadataDecode(t *testing.T) {
|
||||
|
|
|
@ -55,9 +55,9 @@ type Config struct {
|
|||
}
|
||||
|
||||
// String implements fmt.Stringer and is used for debugging.
|
||||
func (c Config) String() string {
|
||||
func (c *Config) String() string {
|
||||
return fmt.Sprintf(
|
||||
"policy='%s' duration='%v' initialInterval='%v' randomizationFactor='%f' multiplier='%f' maxInterval='%v' maxElapsedTime='%v' maxRetries='%d'",
|
||||
"policy='%v' duration='%v' initialInterval='%v' randomizationFactor='%f' multiplier='%f' maxInterval='%v' maxElapsedTime='%v' maxRetries='%d'",
|
||||
c.Policy, c.Duration, c.InitialInterval, c.RandomizationFactor, c.Multiplier, c.MaxInterval, c.MaxElapsedTime, c.MaxRetries,
|
||||
)
|
||||
}
|
||||
|
@ -204,8 +204,8 @@ func (p *PolicyType) DecodeString(value string) error {
|
|||
}
|
||||
|
||||
// String implements fmt.Stringer and is used for debugging.
|
||||
func (p PolicyType) String() string {
|
||||
switch p {
|
||||
func (p *PolicyType) String() string {
|
||||
switch *p {
|
||||
case PolicyConstant:
|
||||
return "constant"
|
||||
case PolicyExponential:
|
||||
|
|
|
@ -241,7 +241,7 @@ func TestRetryNotifyRecoverCancel(t *testing.T) {
|
|||
|
||||
var notifyCalls, recoveryCalls int
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
b := config.NewBackOffWithContext(ctx)
|
||||
errC := make(chan error, 1)
|
||||
startedC := make(chan struct{}, 100)
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
)
|
||||
|
||||
// Algorithm used to wrap the file key.
|
||||
//
|
||||
//nolint:recvcheck
|
||||
type KeyAlgorithm string
|
||||
|
||||
const (
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
)
|
||||
|
||||
// Cipher used to encrypt the file.
|
||||
//
|
||||
//nolint:recvcheck
|
||||
type Cipher string
|
||||
|
||||
const (
|
||||
|
|
|
@ -15,7 +15,6 @@ package v1
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -120,7 +119,7 @@ func TestFileKey(t *testing.T) {
|
|||
// Validate that headerMessage returns the right message, and that there's a newline at the end
|
||||
const manifest = `{"foo":"bar"}`
|
||||
const expect = SchemeName + "\n" + manifest + "\n"
|
||||
fmt.Println(hex.EncodeToString([]byte(expect)))
|
||||
t.Log(hex.EncodeToString([]byte(expect)))
|
||||
|
||||
got := fileKey{}.headerMessage([]byte(manifest))
|
||||
require.Equal(t, expect, string(got))
|
||||
|
|
|
@ -34,11 +34,11 @@ var (
|
|||
|
||||
func TestScheme(t *testing.T) {
|
||||
// Fake wrapKeyFn and unwrapKeyFn, which just return the plaintext key
|
||||
//nolint:stylecheck,revive
|
||||
//nolint:stylecheck
|
||||
var wrapKeyFn WrapKeyFn = func(plaintextKey []byte, algorithm, keyName string, nonce []byte) (wrappedKey []byte, tag []byte, err error) {
|
||||
return plaintextKey, nil, nil
|
||||
}
|
||||
//nolint:stylecheck,revive
|
||||
//nolint:stylecheck
|
||||
var unwrapKeyFn UnwrapKeyFn = func(wrappedKey []byte, algorithm, keyName string, nonce, tag []byte) (plaintextKey []byte, err error) {
|
||||
return wrappedKey, nil
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func TestScheme(t *testing.T) {
|
|||
// Second, check that the JSON manifest is present and valid
|
||||
start := idx + 1
|
||||
idx = bytes.IndexByte(encData[start:], '\n')
|
||||
require.Greater(t, idx, 0)
|
||||
require.Positive(t, idx)
|
||||
var manifest Manifest
|
||||
err = json.Unmarshal(encData[start:(start+idx)], &manifest)
|
||||
require.NoError(t, err)
|
||||
|
@ -106,7 +106,7 @@ func TestScheme(t *testing.T) {
|
|||
// We are not validating the MAC here as the decryption code will do it; we'll just check it's present and 44-byte long (when encoded as base64)
|
||||
start += idx + 1
|
||||
idx = bytes.IndexByte(encData[start:], '\n')
|
||||
require.Greater(t, idx, 0)
|
||||
require.Positive(t, idx)
|
||||
require.Len(t, encData[start:(start+idx)], 44)
|
||||
|
||||
// Decrypt the encrypted data
|
||||
|
|
|
@ -11,7 +11,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
package strings
|
||||
|
||||
import (
|
||||
"path/filepath"
|
|
@ -192,6 +192,7 @@ func ParseDuration(from string) (int, int, int, time.Duration, int, error) {
|
|||
// - ISO8601 duration format
|
||||
// - time.Duration string format
|
||||
// - RFC3339 datetime format
|
||||
// - RFC3339nano datetime format
|
||||
// For duration formats, an offset is added.
|
||||
func ParseTime(from string, offset *time.Time) (time.Time, error) {
|
||||
var start time.Time
|
||||
|
@ -210,8 +211,9 @@ func ParseTime(from string, offset *time.Time) (time.Time, error) {
|
|||
if dur, err = time.ParseDuration(from); err == nil {
|
||||
return start.Add(dur), nil
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339, from); err == nil {
|
||||
if t, err := time.Parse(time.RFC3339Nano, from); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return time.Time{}, errors.New("unsupported time/duration format: " + from)
|
||||
}
|
||||
|
|
|
@ -212,6 +212,13 @@ func TestParseTime(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, time.Duration(0), expected.Sub(tm))
|
||||
})
|
||||
t.Run("parse RFC3339nano datetime", func(t *testing.T) {
|
||||
dummy := time.Now().Add(1000 * time.Nanosecond)
|
||||
expected := time.Now().Add(100 * time.Nanosecond)
|
||||
tm, err := ParseTime(expected.Format(time.RFC3339Nano), &dummy)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, time.Duration(0), expected.Sub(tm))
|
||||
})
|
||||
t.Run("parse empty string", func(t *testing.T) {
|
||||
_, err := ParseTime("", nil)
|
||||
require.ErrorContains(t, err, "unsupported time/duration format")
|
||||
|
|
|
@ -42,7 +42,7 @@ func TestCache(t *testing.T) {
|
|||
cache.Set("key4", "val4", 5)
|
||||
|
||||
// Retrieve values
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := range 16 {
|
||||
v, ok := cache.Get("key1")
|
||||
if i < 2 {
|
||||
require.True(t, ok)
|
||||
|
|
29
utils/env.go
29
utils/env.go
|
@ -1,29 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetEnvDurationWithRange returns the time.Duration value of the environment variable specified by `envVar`.
|
||||
// If the environment variable is not set, it returns `defaultValue`.
|
||||
// If the value is set but is not valid (not a valid time.Duration or falls outside the specified range
|
||||
// [minValue, maxValue] inclusively), it returns `defaultValue` and an error.
|
||||
func GetEnvDurationWithRange(envVar string, defaultValue, min, max time.Duration) (time.Duration, error) {
|
||||
v := os.Getenv(envVar)
|
||||
if v == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
val, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return defaultValue, fmt.Errorf("invalid time.Duration value %s for the %s env variable: %w", val, envVar, err)
|
||||
}
|
||||
|
||||
if val < min || val > max {
|
||||
return defaultValue, fmt.Errorf("invalid value for the %s env variable: value should be between %s and %s, got %s", envVar, min, max, val)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
41
utils/pem.go
41
utils/pem.go
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Dapr Authors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetPEM loads a PEM-encoded file (certificate or key).
|
||||
func GetPEM(val string) ([]byte, error) {
|
||||
// If val is already a PEM-encoded string, return it as-is
|
||||
if IsValidPEM(val) {
|
||||
return []byte(val), nil
|
||||
}
|
||||
|
||||
// Assume it's a file
|
||||
pemBytes, err := os.ReadFile(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value is neither a valid file path or nor a valid PEM-encoded string: %w", err)
|
||||
}
|
||||
return pemBytes, nil
|
||||
}
|
||||
|
||||
// IsValidPEM validates the provided input has PEM formatted block.
|
||||
func IsValidPEM(val string) bool {
|
||||
block, _ := pem.Decode([]byte(val))
|
||||
return block != nil
|
||||
}
|
Loading…
Reference in New Issue