Skip to content

Commit 5978acb

Browse files
committed
feat(api): restrict timeline buckets per check config
1 parent 102f6ce commit 5978acb

File tree

4 files changed

+72
-28
lines changed

4 files changed

+72
-28
lines changed

examples/checks/api-checks.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ checks:
1010
retries: 3
1111
failure_threshold: 3
1212
success_threshold: 1
13+
allowed_buckets: [ minute, hour ]
1314
tags: [ public, critical ]
1415
spec:
1516
url: http://localhost:8080
@@ -38,6 +39,7 @@ checks:
3839
retries: 3
3940
failure_threshold: 3
4041
success_threshold: 1
42+
allowed_buckets: [ minute, hour ]
4143
tags: [ public, critical ]
4244
spec:
4345
host: localhost
@@ -57,6 +59,7 @@ checks:
5759
retries: 3
5860
failure_threshold: 3
5961
success_threshold: 1
62+
allowed_buckets: [ minute, hour ]
6063
tags: [ internal, critical ]
6164
spec:
6265
host: localhost
@@ -80,6 +83,7 @@ checks:
8083
retries: 3
8184
failure_threshold: 3
8285
success_threshold: 1
86+
allowed_buckets: [ minute, hour ]
8387
tags: [ public, critical ]
8488
spec:
8589
server: 8.8.8.8
@@ -100,6 +104,7 @@ checks:
100104
retries: 2
101105
failure_threshold: 2
102106
success_threshold: 1
107+
allowed_buckets: [ hour, day ]
103108
tags: [ public, security ]
104109
spec:
105110
host: api.example.com

internal/api/handlers.go

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -218,51 +218,91 @@ func (h *Handler) timelineFilterFromRequest(
218218

219219
filter.Bucket = model.CheckExecutionBucket(query.Get("bucket"))
220220

221-
interval, err := h.checkInterval(filter.ServiceID, filter.CheckID)
221+
fields, err := h.checkFields(filter.ServiceID, filter.CheckID)
222222
if err != nil {
223223
return filter, err
224224
}
225-
filter.Interval = interval
225+
filter.Interval = fields.Interval
226+
227+
if !isAllowedBucket(filter.Bucket, fields.AllowedBuckets) {
228+
return filter, fmt.Errorf(
229+
"bucket %q is not allowed for check %s/%s; allowed buckets: %v",
230+
filter.Bucket,
231+
filter.ServiceID,
232+
filter.CheckID,
233+
allowedBuckets(fields.AllowedBuckets),
234+
)
235+
}
226236

227237
return filter, filter.Validate()
228238
}
229239

230-
func (h *Handler) checkInterval(serviceID, checkID string) (time.Duration, error) {
240+
func allowedBuckets(allowed []string) []string {
241+
if len(allowed) > 0 {
242+
return allowed
243+
}
244+
245+
return []string{
246+
string(model.CheckExecutionBucketMinute),
247+
string(model.CheckExecutionBucketHour),
248+
}
249+
}
250+
251+
func isAllowedBucket(bucket model.CheckExecutionBucket, allowed []string) bool {
252+
if bucket == "" {
253+
bucket = model.CheckExecutionBucketMinute
254+
}
255+
256+
if len(allowed) == 0 {
257+
return bucket == model.CheckExecutionBucketMinute ||
258+
bucket == model.CheckExecutionBucketHour
259+
}
260+
261+
for i := range allowed {
262+
if model.CheckExecutionBucket(allowed[i]) == bucket {
263+
return true
264+
}
265+
}
266+
267+
return false
268+
}
269+
270+
func (h *Handler) checkFields(serviceID, checkID string) (config.CheckFields, error) {
231271
cfg := h.cfgProvider.Current()
232272

233-
if interval, ok := checkInterval(cfg.HttpChecks, serviceID, checkID); ok {
234-
return interval, nil
273+
if fields, ok := checkFields(cfg.HttpChecks, serviceID, checkID); ok {
274+
return fields, nil
235275
}
236276

237-
if interval, ok := checkInterval(cfg.TCPChecks, serviceID, checkID); ok {
238-
return interval, nil
277+
if fields, ok := checkFields(cfg.TCPChecks, serviceID, checkID); ok {
278+
return fields, nil
239279
}
240280

241-
if interval, ok := checkInterval(cfg.GRPCChecks, serviceID, checkID); ok {
242-
return interval, nil
281+
if fields, ok := checkFields(cfg.GRPCChecks, serviceID, checkID); ok {
282+
return fields, nil
243283
}
244284

245-
if interval, ok := checkInterval(cfg.DNSChecks, serviceID, checkID); ok {
246-
return interval, nil
285+
if fields, ok := checkFields(cfg.DNSChecks, serviceID, checkID); ok {
286+
return fields, nil
247287
}
248288

249-
if interval, ok := checkInterval(cfg.TLSChecks, serviceID, checkID); ok {
250-
return interval, nil
289+
if fields, ok := checkFields(cfg.TLSChecks, serviceID, checkID); ok {
290+
return fields, nil
251291
}
252292

253-
return 0, fmt.Errorf("check %s/%s not found", serviceID, checkID)
293+
return config.CheckFields{}, fmt.Errorf("check %s/%s not found", serviceID, checkID)
254294
}
255295

256-
func checkInterval[T any](
296+
func checkFields[T any](
257297
checks map[string]config.TypedCheck[T],
258298
serviceID string,
259299
checkID string,
260-
) (time.Duration, bool) {
300+
) (config.CheckFields, bool) {
261301
for i := range checks {
262302
if checks[i].Service == serviceID && checks[i].ID == checkID {
263-
return checks[i].Interval, true
303+
return checks[i].CheckFields, true
264304
}
265305
}
266306

267-
return 0, false
307+
return config.CheckFields{}, false
268308
}

internal/app/manager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,5 +390,6 @@ func sameCheckFields(a, b config.CheckFields) bool {
390390
a.FailureThreshold == b.FailureThreshold &&
391391
a.SuccessThreshold == b.SuccessThreshold &&
392392
a.Interval == b.Interval &&
393+
slices.Equal(a.AllowedBuckets, b.AllowedBuckets) &&
393394
a.Enabled == b.Enabled
394395
}

internal/config/model.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,20 @@ type Service struct {
6565
Description string `yaml:"description" json:"description" validate:"omitempty,min=1"`
6666
}
6767

68+
//nolint:lll
6869
type CheckFields struct {
69-
ID string `yaml:"id" json:"id" validate:"required,min=1"`
70-
Name string `yaml:"name" json:"name" validate:"required,min=1"`
71-
Service string `yaml:"service" json:"service" validate:"required,min=1"`
72-
73-
Type CheckType `yaml:"type" json:"type" validate:"required,oneof=http tcp grpc tls dns"`
74-
75-
//nolint:lll,nolintlint
76-
Tags []string `yaml:"tags" json:"tags" validate:"omitempty,min=1,dive,min=1"`
77-
78-
Timeout time.Duration `yaml:"timeout" json:"timeout" validate:"required,gt=0ms"`
70+
ID string `yaml:"id" json:"id" validate:"required,min=1"`
71+
Name string `yaml:"name" json:"name" validate:"required,min=1"`
72+
Service string `yaml:"service" json:"service" validate:"required,min=1"`
73+
Type CheckType `yaml:"type" json:"type" validate:"required,oneof=http tcp grpc tls dns"`
74+
Tags []string `yaml:"tags" json:"tags" validate:"omitempty,min=1,dive,min=1"`
75+
AllowedBuckets []string `yaml:"allowed_buckets" json:"allowed_buckets" validate:"omitempty,dive,oneof=second minute hour day"`
7976
Jitter time.Duration `yaml:"jitter" json:"jitter" validate:"gte=0"`
8077
Retries int `yaml:"retries" json:"retries" validate:"required,gte=0"`
8178
FailureThreshold int `yaml:"failure_threshold" json:"failure_threshold" validate:"required,gte=1"`
8279
SuccessThreshold int `yaml:"success_threshold" json:"success_threshold" validate:"required,gte=1"`
8380
Interval time.Duration `yaml:"interval" json:"interval" validate:"required,gt=0ms"`
81+
Timeout time.Duration `yaml:"timeout" json:"timeout" validate:"required,gt=0ms"`
8482
Enabled bool `yaml:"enabled" json:"enabled"`
8583
}
8684

0 commit comments

Comments
 (0)