Files
Kent GruberandGitHub eadf754dc8 Use better string splitting techniques where possible (#8226)
## What changed?

This PR aims to avoid usage of
[`strings.Split`](https://pkg.go.dev/strings#Split) where possible in
favor of better string splitting techniques, speficially:
[`strings.SplitN`](https://pkg.go.dev/strings#SplitN) and
[`strings.SplitSeq`](https://pkg.go.dev/strings#SplitSeq) where
appropriate.

There was also a [`strings.Fields`](https://pkg.go.dev/strings#Fields)
change I made to use
[`strings.FieldsSeq`](https://pkg.go.dev/strings#FieldsSeq) instead, and
another for S3 to use the [`path`](https://pkg.go.dev/path) package
instead of [`strings.Split`](https://pkg.go.dev/strings#Split).

## Why?

[`strings.SplitN`](https://pkg.go.dev/strings#SplitN) and
[`strings.SplitSeq`](https://pkg.go.dev/strings#SplitSeq) are often
better options in many cases, and can be _partially_ detected using
[`modernize`](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize):
> `stringsseq`: replace Split in "for range strings.Split(...)" by
go1.24's more efficient `SplitSeq`, or `Fields` with `FieldSeq`.

## How did you test it?
- [X] built
- [X] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks

There are lots of potentially subtle behaviors from the `strings.Split`
(and `strings.Fields`) usage that should be accounted for. If our
existing tests don't cover those subtleties, there's risk for
introducing an unintended bug. More intricate handling/parsing
previously using the `strings` package should get extra attention from
reviewers. I've attempted to break up my changes into logical commit
chunks to aid in review / help spot potentially concerning changes.
2025-08-26 13:23:40 -04:00

110 lines
3.3 KiB
Go

package callbacks
import (
"regexp"
"strings"
"time"
"go.temporal.io/server/common/backoff"
"go.temporal.io/server/common/dynamicconfig"
)
var RequestTimeout = dynamicconfig.NewDestinationDurationSetting(
"component.callbacks.request.timeout",
time.Second*10,
`RequestTimeout is the timeout for executing a single callback request.`,
)
var RetryPolicyInitialInterval = dynamicconfig.NewGlobalDurationSetting(
"component.callbacks.retryPolicy.initialInterval",
time.Second,
`The initial backoff interval between every callback request attempt for a given callback.`,
)
var RetryPolicyMaximumInterval = dynamicconfig.NewGlobalDurationSetting(
"component.callbacks.retryPolicy.maxInterval",
time.Hour,
`The maximum backoff interval between every callback request attempt for a given callback.`,
)
type Config struct {
RequestTimeout dynamicconfig.DurationPropertyFnWithDestinationFilter
RetryPolicy func() backoff.RetryPolicy
}
func ConfigProvider(dc *dynamicconfig.Collection) *Config {
return &Config{
RequestTimeout: RequestTimeout.Get(dc),
RetryPolicy: func() backoff.RetryPolicy {
return backoff.NewExponentialRetryPolicy(
RetryPolicyInitialInterval.Get(dc)(),
).WithMaximumInterval(
RetryPolicyMaximumInterval.Get(dc)(),
).WithExpirationInterval(
backoff.NoInterval,
)
},
}
}
var AllowedAddresses = dynamicconfig.NewNamespaceTypedSettingWithConverter(
"component.callbacks.allowedAddresses",
allowedAddressConverter,
[]AddressMatchRule(nil),
`The per-namespace list of addresses that are allowed for callbacks and whether secure connections (https) are required.
URLs are checked against each in order when starting a workflow with attached callbacks and only need to match one to pass validation.
Default is no address rules, meaning all callbacks will be rejected. Any invalid entries are ignored. Each entry is a map with possible values:
- "Pattern":string (required) the host:port pattern to which this config applies.
Wildcards, '*', are supported and can match any number of characters (e.g. '*' matches everything, 'prefix.*.domain' matches 'prefix.a.domain' as well as 'prefix.a.b.domain').
- "AllowInsecure":bool (optional, default=false) indicates whether https is required`)
type AddressMatchRule struct {
Regexp *regexp.Regexp
AllowInsecure bool
}
func allowedAddressConverter(val any) ([]AddressMatchRule, error) {
type entry struct {
Pattern string
AllowInsecure bool
}
intermediate, err := dynamicconfig.ConvertStructure[[]entry](nil)(val)
if err != nil {
return nil, err
}
var configs []AddressMatchRule
for _, e := range intermediate {
if e.Pattern == "" {
// Skip configs with missing / unparsable Pattern
continue
}
re, err := regexp.Compile(addressPatternToRegexp(e.Pattern))
if err != nil {
// Skip configs with malformed Pattern
continue
}
configs = append(configs, AddressMatchRule{
Regexp: re,
AllowInsecure: e.AllowInsecure,
})
}
return configs, nil
}
func addressPatternToRegexp(pattern string) string {
var result strings.Builder
result.WriteString("^")
first := true
for literal := range strings.SplitSeq(pattern, "*") {
if !first {
// Replace * with .*
result.WriteString(".*")
}
result.WriteString(regexp.QuoteMeta(literal))
first = false
}
result.WriteString("$")
return result.String()
}