Compare commits
1 Commits
9b3baec214
...
ea92c616c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea92c616c2 |
19
README.md
19
README.md
@@ -93,7 +93,7 @@ TTY detection, and security headers are always applied.
|
|||||||
| `METRICS_USERNAME` | Basic auth username for `/metrics` | `""` |
|
| `METRICS_USERNAME` | Basic auth username for `/metrics` | `""` |
|
||||||
| `METRICS_PASSWORD` | Basic auth password for `/metrics` | `""` |
|
| `METRICS_PASSWORD` | Basic auth password for `/metrics` | `""` |
|
||||||
| `SENTRY_DSN` | Sentry error reporting DSN | `""` |
|
| `SENTRY_DSN` | Sentry error reporting DSN | `""` |
|
||||||
| `RETENTION_SWEEP_INTERVAL` | How often the retention reaper and archive sweeper run (Go duration, must be positive) | `1h` |
|
| `RETENTION_SWEEP_INTERVAL` | How often the retention reaper and archive sweeper run (Go duration) | `1h` |
|
||||||
| `SESSION_IDLE_TIMEOUT` | Idle session timeout (Go duration) | `24h` |
|
| `SESSION_IDLE_TIMEOUT` | Idle session timeout (Go duration) | `24h` |
|
||||||
| `RECEIVER_RATE_LIMIT` | Receiver requests/minute per IP per entrypoint | `120` |
|
| `RECEIVER_RATE_LIMIT` | Receiver requests/minute per IP per entrypoint | `120` |
|
||||||
| `TRUSTED_PROXIES` | CIDRs whose forwarded headers are trusted | `""` (none) |
|
| `TRUSTED_PROXIES` | CIDRs whose forwarded headers are trusted | `""` (none) |
|
||||||
@@ -174,12 +174,8 @@ its value and refuses to start, rather than silently running with a
|
|||||||
substituted default. `PORT=eighty`, `DEBUG=ture`, and
|
substituted default. `PORT=eighty`, `DEBUG=ture`, and
|
||||||
`RETENTION_SWEEP_INTERVAL=1 hour` all abort startup. `PORT` must
|
`RETENTION_SWEEP_INTERVAL=1 hour` all abort startup. `PORT` must
|
||||||
additionally be a number in the range 1–65535,
|
additionally be a number in the range 1–65535,
|
||||||
`RECEIVER_RATE_LIMIT` must be at least 1,
|
`RECEIVER_RATE_LIMIT` must be at least 1, and every entry in
|
||||||
`RETENTION_SWEEP_INTERVAL` must be greater than zero (it is a ticker
|
`TRUSTED_PROXIES` must be a CIDR block or a bare IP address.
|
||||||
period, so `0s` or a negative value would crash the reaper after
|
|
||||||
startup), and every entry in `TRUSTED_PROXIES` must be a CIDR block or
|
|
||||||
a bare IP address. `SESSION_IDLE_TIMEOUT` is the exception: a
|
|
||||||
non-positive value there means idle expiry is disabled, not invalid.
|
|
||||||
|
|
||||||
Boolean variables (`DEBUG`, `MAINTENANCE_MODE`) accept exactly the
|
Boolean variables (`DEBUG`, `MAINTENANCE_MODE`) accept exactly the
|
||||||
spellings Go's `strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`,
|
spellings Go's `strconv.ParseBool` accepts — `1`, `t`, `T`, `TRUE`,
|
||||||
@@ -504,7 +500,7 @@ A programmatic access credential for API authentication.
|
|||||||
#### Event
|
#### Event
|
||||||
|
|
||||||
A captured incoming webhook request. Stores the complete HTTP request
|
A captured incoming webhook request. Stores the complete HTTP request
|
||||||
data for auditing and for the planned replay capability.
|
data for replay and auditing.
|
||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
| -------------- | ------ | ----------- |
|
| -------------- | ------ | ----------- |
|
||||||
@@ -786,10 +782,9 @@ unknown) one while one of its deliveries is still `retrying`, both
|
|||||||
recovery paths above terminally mark that delivery `failed` and record a
|
recovery paths above terminally mark that delivery `failed` and record a
|
||||||
`DeliveryResult` naming the current target type as the reason, logging it
|
`DeliveryResult` naming the current target type as the reason, logging it
|
||||||
at warn level. The delivery is not re-dispatched under the new type — the
|
at warn level. The delivery is not re-dispatched under the new type — the
|
||||||
operator never asked for that delivery — and while the event itself
|
operator never asked for that delivery — and the event itself remains
|
||||||
remains stored in the per-webhook event database, there is no way to
|
stored in the per-webhook event database, so it can be redelivered
|
||||||
redeliver it: manual redelivery is planned, not implemented (see
|
manually.
|
||||||
[TODO.md](TODO.md)).
|
|
||||||
|
|
||||||
### Circuit Breaker (HTTP Targets with Retries)
|
### Circuit Breaker (HTTP Targets with Retries)
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ type Config struct {
|
|||||||
SentryDSN string
|
SentryDSN string
|
||||||
|
|
||||||
// RetentionSweepInterval is how often the retention reaper runs.
|
// RetentionSweepInterval is how often the retention reaper runs.
|
||||||
// Always positive: it becomes a time.NewTicker period.
|
|
||||||
RetentionSweepInterval time.Duration
|
RetentionSweepInterval time.Duration
|
||||||
|
|
||||||
// SessionIdleTimeout is the sliding inactivity window after
|
// SessionIdleTimeout is the sliding inactivity window after
|
||||||
@@ -236,34 +235,6 @@ func envDuration(
|
|||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// envPositiveDuration returns the value of the named environment
|
|
||||||
// variable parsed as a Go duration that must be greater than zero.
|
|
||||||
// Returns defaultValue if not set. A set value that is unparseable or
|
|
||||||
// non-positive is a hard error naming the key and the bad value.
|
|
||||||
//
|
|
||||||
// This is for durations that reach time.NewTicker, which panics on a
|
|
||||||
// non-positive period, in a goroutine started after startup has
|
|
||||||
// already reported success. It is deliberately not used for durations
|
|
||||||
// where non-positive means "disabled" (SESSION_IDLE_TIMEOUT).
|
|
||||||
func envPositiveDuration(
|
|
||||||
key string,
|
|
||||||
defaultValue time.Duration,
|
|
||||||
) (time.Duration, error) {
|
|
||||||
d, err := envDuration(key, defaultValue)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if d <= 0 {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"%w: %s must be greater than zero, got %s",
|
|
||||||
ErrNonPositiveValue, key, d,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCIDR parses one trusted-proxy list entry, which may be a
|
// parseCIDR parses one trusted-proxy list entry, which may be a
|
||||||
// CIDR block ("10.0.0.0/8") or a bare address ("10.0.0.1", treated
|
// CIDR block ("10.0.0.0/8") or a bare address ("10.0.0.1", treated
|
||||||
// as a single-host block).
|
// as a single-host block).
|
||||||
@@ -375,7 +346,7 @@ func loadFromEnv() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
retentionSweepInterval, err := envPositiveDuration(
|
retentionSweepInterval, err := envDuration(
|
||||||
"RETENTION_SWEEP_INTERVAL",
|
"RETENTION_SWEEP_INTERVAL",
|
||||||
defaultRetentionSweepInterval,
|
defaultRetentionSweepInterval,
|
||||||
)
|
)
|
||||||
@@ -383,8 +354,6 @@ func loadFromEnv() (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-positive is "disabled" here, not invalid, so this stays on
|
|
||||||
// envDuration.
|
|
||||||
sessionIdleTimeout, err := envDuration(
|
sessionIdleTimeout, err := envDuration(
|
||||||
"SESSION_IDLE_TIMEOUT",
|
"SESSION_IDLE_TIMEOUT",
|
||||||
defaultSessionIdleTimeout,
|
defaultSessionIdleTimeout,
|
||||||
|
|||||||
@@ -139,10 +139,6 @@ func TestRetentionSweepInterval(t *testing.T) {
|
|||||||
set bool
|
set bool
|
||||||
value string
|
value string
|
||||||
expectError bool
|
expectError bool
|
||||||
// sentinel, when set, must be wrapped by the startup
|
|
||||||
// error; every error case must additionally name the
|
|
||||||
// variable in its message.
|
|
||||||
sentinel error
|
|
||||||
expected time.Duration
|
expected time.Duration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -162,24 +158,6 @@ func TestRetentionSweepInterval(t *testing.T) {
|
|||||||
value: "not-a-duration",
|
value: "not-a-duration",
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// A non-positive period panics the ticker in the
|
|
||||||
// reaper and archive-sweeper goroutines, long after
|
|
||||||
// startup has reported success, so it has to fail
|
|
||||||
// here instead.
|
|
||||||
name: "zero fails startup",
|
|
||||||
set: true,
|
|
||||||
value: "0s",
|
|
||||||
expectError: true,
|
|
||||||
sentinel: config.ErrNonPositiveValue,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "negative fails startup",
|
|
||||||
set: true,
|
|
||||||
value: "-1h",
|
|
||||||
expectError: true,
|
|
||||||
sentinel: config.ErrNonPositiveValue,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -197,9 +175,7 @@ func TestRetentionSweepInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tt.expectError {
|
if tt.expectError {
|
||||||
expectStartupErrorFor(
|
expectStartupError(t)
|
||||||
t, "RETENTION_SWEEP_INTERVAL", tt.sentinel,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
testRetentionSweepIntervalSuccess(t, tt.expected)
|
testRetentionSweepIntervalSuccess(t, tt.expected)
|
||||||
}
|
}
|
||||||
@@ -305,22 +281,6 @@ func TestSessionIdleTimeout(t *testing.T) {
|
|||||||
value: "not-a-duration",
|
value: "not-a-duration",
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// Non-positive is "idle expiry disabled" for this
|
|
||||||
// variable, not a configuration error: unlike
|
|
||||||
// RETENTION_SWEEP_INTERVAL it never becomes a ticker
|
|
||||||
// period.
|
|
||||||
name: "zero disables idle expiry",
|
|
||||||
set: true,
|
|
||||||
value: "0s",
|
|
||||||
expected: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "negative disables idle expiry",
|
|
||||||
set: true,
|
|
||||||
value: "-1h",
|
|
||||||
expected: -time.Hour,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -785,9 +785,9 @@ func (e *Engine) sweepSingleRetry(
|
|||||||
// status retrying themselves. Re-dispatching under the new type
|
// status retrying themselves. Re-dispatching under the new type
|
||||||
// would be a delivery the operator never asked for, and leaving
|
// would be a delivery the operator never asked for, and leaving
|
||||||
// the row retrying strands it forever, so the delivery is
|
// the row retrying strands it forever, so the delivery is
|
||||||
// failed with a recorded reason. The event stays stored, but
|
// failed with a recorded reason and can be redelivered
|
||||||
// nothing redelivers it today. Logged at warn, not error: this
|
// manually. Logged at warn, not error: this is operator-caused
|
||||||
// is operator-caused state, not a system fault.
|
// state, not a system fault.
|
||||||
func (e *Engine) failUnretryableRetry(
|
func (e *Engine) failUnretryableRetry(
|
||||||
webhookDB *gorm.DB,
|
webhookDB *gorm.DB,
|
||||||
webhookID string,
|
webhookID string,
|
||||||
|
|||||||
Reference in New Issue
Block a user