Skip to content

Commit 71b78cf

Browse files
gotjoshpracucci
andauthored
Ruler Experimental API: Fix rule group validation (#3210)
* Ruler Experimental API: Fix rule group validation Fixed a few cases where the API would allow us to submit a bad rule group payload which would end up overwriting existing ones. Signed-off-by: gotjosh <[email protected]> * Add changelog Signed-off-by: gotjosh <[email protected]> * Appease the linter Signed-off-by: gotjosh <[email protected]> Co-authored-by: Marco Pracucci <[email protected]>
1 parent 56fafbe commit 71b78cf

File tree

4 files changed

+88
-22
lines changed

4 files changed

+88
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* [BUGFIX] Ruler: directories in the configured `rules-path` will be removed on startup and shutdown in order to ensure they don't persist between runs. #3195
1515
* [BUGFIX] Handle hash-collisions in the query path. #3192
1616
* [BUGFIX] Check for postgres rows errors. #3197
17+
* [BUGFIX] Ruler Experimental API: Don't allow rule groups without names or empty rule groups. #3210
1718
* [BUGFIX] Experimental Alertmanager API: Do not allow empty Alertmanager configurations or bad template filenames to be submitted through the configuration API. #3185
1819

1920
## 1.4.0-rc.0 in progress

pkg/ruler/api.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/url"
88
"sort"
99
"strconv"
10+
"strings"
1011
"time"
1112

1213
"github.com/go-kit/kit/log"
@@ -443,10 +444,13 @@ func (r *Ruler) CreateRuleGroup(w http.ResponseWriter, req *http.Request) {
443444

444445
errs := r.manager.ValidateRuleGroup(rg)
445446
if len(errs) > 0 {
447+
e := []string{}
446448
for _, err := range errs {
447449
level.Error(logger).Log("msg", "unable to validate rule group payload", "err", err.Error())
450+
e = append(e, err.Error())
448451
}
449-
http.Error(w, errs[0].Error(), http.StatusBadRequest)
452+
453+
http.Error(w, strings.Join(e, ", "), http.StatusBadRequest)
450454
return
451455
}
452456

pkg/ruler/api_test.go

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ruler
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
io "io"
78
"io/ioutil"
89
"net/http"
@@ -170,7 +171,44 @@ func TestRuler_Create(t *testing.T) {
170171
defer rcleanup()
171172
defer services.StopAndAwaitTerminated(context.Background(), r) //nolint:errcheck
172173

173-
rules := `name: test
174+
tc := []struct {
175+
name string
176+
input string
177+
output string
178+
err error
179+
status int
180+
}{
181+
{
182+
name: "with an empty payload",
183+
input: "",
184+
status: 400,
185+
err: errors.New("invalid rules config: rule group name must not be empty"),
186+
},
187+
{
188+
name: "with no rule group name",
189+
input: `
190+
interval: 15s
191+
rules:
192+
- record: up_rule
193+
expr: up
194+
`,
195+
status: 400,
196+
err: errors.New("invalid rules config: rule group name must not be empty"),
197+
},
198+
{
199+
name: "with no rules",
200+
input: `
201+
name: rg_name
202+
interval: 15s
203+
`,
204+
status: 400,
205+
err: errors.New("invalid rules config: rule group 'rg_name' has no rules"),
206+
},
207+
{
208+
name: "with a a valid rules file",
209+
status: 202,
210+
input: `
211+
name: test
174212
interval: 15s
175213
rules:
176214
- record: up_rule
@@ -182,26 +220,36 @@ rules:
182220
test: test
183221
labels:
184222
test: test
185-
`
186-
187-
router := mux.NewRouter()
188-
router.Path("/api/v1/rules/{namespace}").Methods("POST").HandlerFunc(r.CreateRuleGroup)
189-
router.Path("/api/v1/rules/{namespace}/{groupName}").Methods("GET").HandlerFunc(r.GetRuleGroup)
190-
191-
// POST
192-
req := requestFor(t, http.MethodPost, "https://localhost:8080/api/v1/rules/namespace", strings.NewReader(rules), "user1")
193-
w := httptest.NewRecorder()
194-
195-
router.ServeHTTP(w, req)
196-
require.Equal(t, 202, w.Code)
197-
198-
// GET
199-
req = requestFor(t, http.MethodGet, "https://localhost:8080/api/v1/rules/namespace/test", nil, "user1")
200-
w = httptest.NewRecorder()
201-
202-
router.ServeHTTP(w, req)
203-
require.Equal(t, 200, w.Code)
204-
require.Equal(t, "name: test\ninterval: 15s\nrules:\n - record: up_rule\n expr: up{}\n - alert: up_alert\n expr: sum(up{}) > 1\n for: 30s\n labels:\n test: test\n annotations:\n test: test\n", w.Body.String())
223+
`,
224+
output: "name: test\ninterval: 15s\nrules:\n - record: up_rule\n expr: up{}\n - alert: up_alert\n expr: sum(up{}) > 1\n for: 30s\n labels:\n test: test\n annotations:\n test: test\n",
225+
},
226+
}
227+
228+
for _, tt := range tc {
229+
t.Run(tt.name, func(t *testing.T) {
230+
router := mux.NewRouter()
231+
router.Path("/api/v1/rules/{namespace}").Methods("POST").HandlerFunc(r.CreateRuleGroup)
232+
router.Path("/api/v1/rules/{namespace}/{groupName}").Methods("GET").HandlerFunc(r.GetRuleGroup)
233+
// POST
234+
req := requestFor(t, http.MethodPost, "https://localhost:8080/api/v1/rules/namespace", strings.NewReader(tt.input), "user1")
235+
w := httptest.NewRecorder()
236+
237+
router.ServeHTTP(w, req)
238+
require.Equal(t, tt.status, w.Code)
239+
240+
if tt.err == nil {
241+
// GET
242+
req = requestFor(t, http.MethodGet, "https://localhost:8080/api/v1/rules/namespace/test", nil, "user1")
243+
w = httptest.NewRecorder()
244+
245+
router.ServeHTTP(w, req)
246+
require.Equal(t, 200, w.Code)
247+
require.Equal(t, tt.output, w.Body.String())
248+
} else {
249+
require.Equal(t, tt.err.Error()+"\n", w.Body.String())
250+
}
251+
})
252+
}
205253
}
206254

207255
func TestRuler_DeleteNamespace(t *testing.T) {

pkg/ruler/manager.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package ruler
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67
"sync"
78

89
"github.com/go-kit/kit/log"
910
"github.com/go-kit/kit/log/level"
1011
ot "github.com/opentracing/opentracing-go"
12+
"github.com/pkg/errors"
1113
"github.com/prometheus/client_golang/prometheus"
1214
"github.com/prometheus/client_golang/prometheus/promauto"
1315
"github.com/prometheus/prometheus/config"
@@ -253,6 +255,17 @@ func (r *DefaultMultiTenantManager) Stop() {
253255

254256
func (*DefaultMultiTenantManager) ValidateRuleGroup(g rulefmt.RuleGroup) []error {
255257
var errs []error
258+
259+
if g.Name == "" {
260+
errs = append(errs, errors.New("invalid rules config: rule group name must not be empty"))
261+
return errs
262+
}
263+
264+
if len(g.Rules) == 0 {
265+
errs = append(errs, fmt.Errorf("invalid rules config: rule group '%s' has no rules", g.Name))
266+
return errs
267+
}
268+
256269
for i, r := range g.Rules {
257270
for _, err := range r.Validate() {
258271
var ruleName string

0 commit comments

Comments
 (0)