Skip to content

Commit 4baeedc

Browse files
authored
Added duplicates check (#7)
Sloth 0.12 codebase used
1 parent 3757b66 commit 4baeedc

File tree

11 files changed

+372
-5
lines changed

11 files changed

+372
-5
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ vendor/
2222

2323
# mise/asdf
2424
.tool-versions
25+
26+
# VSCode
27+
.vscode/

cmd/sloth/commands/validate.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/slok/sloth/internal/log"
1616
"github.com/slok/sloth/internal/plugin"
17+
commonerrors "github.com/slok/sloth/pkg/common/errors"
1718
utilsdata "github.com/slok/sloth/pkg/common/utils/data"
1819
slothlib "github.com/slok/sloth/pkg/lib"
1920
)
@@ -28,6 +29,7 @@ type validateCommand struct {
2829
sloPeriod string
2930
sloPlugins []string
3031
disableDefaultSLOPlugins bool
32+
ignoreSloDuplicates bool
3133
}
3234

3335
// NewValidateCommand returns the validate command.
@@ -43,6 +45,7 @@ func NewValidateCommand(app *kingpin.Application) Command {
4345
cmd.Flag("default-slo-period", "The default SLO period windows to be used for the SLOs.").Default("30d").StringVar(&c.sloPeriod)
4446
cmd.Flag("slo-plugins", `SLO plugins chain declaration in JSON format '{"id": "foo","priority": 0,"config": "{}"}' (Can be repeated).`).Short('s').StringsVar(&c.sloPlugins)
4547
cmd.Flag("disable-default-slo-plugins", `Disables the default SLO plugins, normally used along with custom SLO plugins to fully customize Sloth behavior`).BoolVar(&c.disableDefaultSLOPlugins)
48+
cmd.Flag("ignore-slo-duplicates", "Flag to ignore SLO duplicates in specs (service and name used as an SLO/SLI identifier).").Default("false").BoolVar(&c.ignoreSloDuplicates)
4649

4750
return c
4851
}
@@ -117,6 +120,7 @@ func (v validateCommand) Run(ctx context.Context, config RootConfig) error {
117120
// For every file load the data and start the validation process:
118121
validations := []*fileValidation{}
119122
totalValidations := 0
123+
sloIDs := make(map[string]string)
120124
for _, input := range sloPaths {
121125
// Get SLO spec data.
122126
slxData, err := os.ReadFile(input)
@@ -140,9 +144,25 @@ func (v validateCommand) Run(ctx context.Context, config RootConfig) error {
140144
}
141145

142146
// Generate SLOs.
143-
_, err := genService.GenerateFromRaw(ctx, []byte(genTarget.SLOData))
147+
sloGroupResult, err := genService.GenerateFromRaw(ctx, []byte(genTarget.SLOData))
144148
if err != nil {
145149
validation.Errs = append(validation.Errs, fmt.Errorf("invalid SLO: %w", err))
150+
} else {
151+
// Check for SLO duplicates
152+
if !v.ignoreSloDuplicates {
153+
for _, sloResult := range sloGroupResult.SLOResults {
154+
slo := sloResult.SLO
155+
if sloFile, exists := sloIDs[slo.ID]; !exists {
156+
sloIDs[slo.ID] = validation.File
157+
} else {
158+
err := fmt.Errorf(
159+
"SLO duplicated. SLO{service=%s, name=%s}, ID=%s already exists in a file: %s: %w",
160+
slo.Service, slo.Name, slo.ID, sloFile, commonerrors.ErrAlreadyExists,
161+
)
162+
validation.Errs = append(validation.Errs, err)
163+
}
164+
}
165+
}
146166
}
147167
}
148168

pkg/common/errors/errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ var (
1212

1313
// ErrRequired will be used when a required field is not set.
1414
ErrRequired = fmt.Errorf("required")
15+
16+
// ErrAlreadyExists will be used when a resource already exists.
17+
ErrAlreadyExists = fmt.Errorf("already exists")
1518
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
version: "prometheus/v1"
3+
service: "svc01"
4+
labels:
5+
global01k1: global01v1
6+
slos:
7+
- name: "slo1"
8+
objective: 99.9
9+
description: "This is SLO 01."
10+
labels:
11+
global02k1: global02v1
12+
sli:
13+
events:
14+
error_query: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
15+
total_query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
16+
alerting:
17+
name: myServiceAlert
18+
labels:
19+
alert01k1: "alert01v1"
20+
annotations:
21+
alert02k1: "alert02k2"
22+
pageAlert:
23+
labels:
24+
alert03k1: "alert03v1"
25+
ticketAlert:
26+
labels:
27+
alert04k1: "alert04v1"
28+
- name: "slo02"
29+
objective: 95
30+
description: "This is SLO 02."
31+
labels:
32+
global03k1: global03v1
33+
sli:
34+
raw:
35+
error_ratio_query: |
36+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
37+
/
38+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
39+
alerting:
40+
page_alert:
41+
disable: true
42+
ticket_alert:
43+
disable: true
44+
45+
---
46+
version: "prometheus/v1"
47+
service: "svc01"
48+
labels:
49+
global01k1: global01v1
50+
slos:
51+
- name: "slo1" # duplicate
52+
objective: 99.9
53+
description: "This is SLO 01."
54+
labels:
55+
global02k1: global02v1
56+
sli:
57+
events:
58+
error_query: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
59+
total_query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
60+
alerting:
61+
name: myServiceAlert
62+
labels:
63+
alert01k1: "alert01v1"
64+
annotations:
65+
alert02k1: "alert02k2"
66+
pageAlert:
67+
labels:
68+
alert03k1: "alert03v1"
69+
ticketAlert:
70+
labels:
71+
alert04k1: "alert04v1"
72+
- name: "slo02" # duplicate
73+
objective: 95
74+
description: "This is SLO 02."
75+
labels:
76+
global03k1: global03v1
77+
sli:
78+
raw:
79+
error_ratio_query: |
80+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
81+
/
82+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
83+
alerting:
84+
page_alert:
85+
disable: true
86+
ticket_alert:
87+
disable: true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
apiVersion: sloth.slok.dev/v1
2+
kind: PrometheusServiceLevel
3+
metadata:
4+
name: svc
5+
namespace: test-ns
6+
spec:
7+
service: "svc01"
8+
labels:
9+
global01k1: global01v1
10+
slos:
11+
- name: "slo1"
12+
objective: 99.9
13+
description: "This is SLO 01."
14+
labels:
15+
global02k1: global02v1
16+
sli:
17+
events:
18+
errorQuery: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
19+
totalQuery: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
20+
alerting:
21+
name: myServiceAlert
22+
labels:
23+
alert01k1: "alert01v1"
24+
annotations:
25+
alert02k1: "alert02k2"
26+
pageAlert:
27+
labels:
28+
alert03k1: "alert03v1"
29+
ticketAlert:
30+
labels:
31+
alert04k1: "alert04v1"
32+
- name: "slo02"
33+
objective: 95
34+
description: "This is SLO 02."
35+
labels:
36+
global03k1: global03v1
37+
sli:
38+
raw:
39+
errorRatioQuery: |
40+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
41+
/
42+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
43+
alerting:
44+
pageAlert:
45+
disable: true
46+
ticketAlert:
47+
disable: true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
apiVersion: sloth.slok.dev/v1
2+
kind: PrometheusServiceLevel
3+
metadata:
4+
name: svc
5+
namespace: test-ns
6+
spec:
7+
service: "svc01"
8+
labels:
9+
global01k1: global01v1
10+
slos:
11+
- name: "slo1"
12+
objective: 99.9
13+
description: "This is SLO 01."
14+
labels:
15+
global02k1: global02v1
16+
sli:
17+
events:
18+
errorQuery: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
19+
totalQuery: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
20+
alerting:
21+
name: myServiceAlert
22+
labels:
23+
alert01k1: "alert01v1"
24+
annotations:
25+
alert02k1: "alert02k2"
26+
pageAlert:
27+
labels:
28+
alert03k1: "alert03v1"
29+
ticketAlert:
30+
labels:
31+
alert04k1: "alert04v1"
32+
- name: "slo02"
33+
objective: 95
34+
description: "This is SLO 02."
35+
labels:
36+
global03k1: global03v1
37+
sli:
38+
raw:
39+
errorRatioQuery: |
40+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
41+
/
42+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
43+
alerting:
44+
pageAlert:
45+
disable: true
46+
ticketAlert:
47+
disable: true
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: openslo/v1alpha
2+
kind: SLO
3+
metadata:
4+
name: slo1
5+
displayName: Integration test SLO1
6+
spec:
7+
service: svc01
8+
description: "this is SLO1."
9+
budgetingMethod: Occurrences
10+
objectives:
11+
- ratioMetrics:
12+
good:
13+
source: prometheus
14+
queryType: promql
15+
query: sum(rate(http_request_duration_seconds_count{job="myservice",code!~"(5..|429)"}[{{.window}}]))
16+
total:
17+
source: prometheus
18+
queryType: promql
19+
query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
20+
target: 0.999
21+
timeWindows:
22+
- count: 30
23+
unit: Day
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: openslo/v1alpha
2+
kind: SLO
3+
metadata:
4+
name: slo1
5+
displayName: Integration test SLO1
6+
spec:
7+
service: svc01
8+
description: "this is SLO1."
9+
budgetingMethod: Occurrences
10+
objectives:
11+
- ratioMetrics:
12+
good:
13+
source: prometheus
14+
queryType: promql
15+
query: sum(rate(http_request_duration_seconds_count{job="myservice",code!~"(5..|429)"}[{{.window}}]))
16+
total:
17+
source: prometheus
18+
queryType: promql
19+
query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
20+
target: 0.999
21+
timeWindows:
22+
- count: 30
23+
unit: Day
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
version: "prometheus/v1"
2+
service: "svc01"
3+
labels:
4+
global01k1: global01v1
5+
slos:
6+
- name: "slo1"
7+
objective: 99.9
8+
description: "This is SLO 01."
9+
labels:
10+
global02k1: global02v1
11+
sli:
12+
events:
13+
error_query: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
14+
total_query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
15+
alerting:
16+
name: myServiceAlert
17+
labels:
18+
alert01k1: "alert01v1"
19+
annotations:
20+
alert02k1: "alert02k2"
21+
pageAlert:
22+
labels:
23+
alert03k1: "alert03v1"
24+
ticketAlert:
25+
labels:
26+
alert04k1: "alert04v1"
27+
- name: "slo02"
28+
objective: 95
29+
description: "This is SLO 02."
30+
labels:
31+
global03k1: global03v1
32+
sli:
33+
raw:
34+
error_ratio_query: |
35+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
36+
/
37+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
38+
alerting:
39+
page_alert:
40+
disable: true
41+
ticket_alert:
42+
disable: true
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
version: "prometheus/v1"
2+
service: "svc01"
3+
labels:
4+
global01k1: global01v1
5+
slos:
6+
- name: "slo1"
7+
objective: 99.9
8+
description: "This is SLO 01."
9+
labels:
10+
global02k1: global02v1
11+
sli:
12+
events:
13+
error_query: sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
14+
total_query: sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
15+
alerting:
16+
name: myServiceAlert
17+
labels:
18+
alert01k1: "alert01v1"
19+
annotations:
20+
alert02k1: "alert02k2"
21+
pageAlert:
22+
labels:
23+
alert03k1: "alert03v1"
24+
ticketAlert:
25+
labels:
26+
alert04k1: "alert04v1"
27+
- name: "slo02"
28+
objective: 95
29+
description: "This is SLO 02."
30+
labels:
31+
global03k1: global03v1
32+
sli:
33+
raw:
34+
error_ratio_query: |
35+
sum(rate(http_request_duration_seconds_count{job="myservice",code=~"(5..|429)"}[{{.window}}]))
36+
/
37+
sum(rate(http_request_duration_seconds_count{job="myservice"}[{{.window}}]))
38+
alerting:
39+
page_alert:
40+
disable: true
41+
ticket_alert:
42+
disable: true

0 commit comments

Comments
 (0)