Skip to content

Commit 5d88330

Browse files
committed
refactor per PR comments
Signed-off-by: Jacob Lisi <[email protected]>
1 parent f1d626f commit 5d88330

File tree

3 files changed

+81
-110
lines changed

3 files changed

+81
-110
lines changed

integration/api_ruler_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ func TestRulerAPI(t *testing.T) {
3232
c, err := e2ecortex.NewClient("", "", "", ruler.HTTPEndpoint(), "user-1")
3333
require.NoError(t, err)
3434

35-
// Create example namespace and rule group to use for tests
36-
namespace := "test_namespace"
35+
// Create example namespace and rule group to use for tests, using strings that
36+
// require url escaping.
37+
namespace := "test_encoded_+namespace?"
3738
rg := rulefmt.RuleGroup{
38-
Name: "test_group",
39+
Name: "test_encoded_+\"+group_name?",
3940
Interval: 100,
4041
Rules: []rulefmt.Rule{
4142
rulefmt.Rule{

integration/e2ecortex/client.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io/ioutil"
99
"net/http"
10+
"net/url"
1011
"time"
1112

1213
"github.com/gogo/protobuf/proto"
@@ -199,7 +200,7 @@ func (c *Client) SetRuleGroup(rulegroup rulefmt.RuleGroup, namespace string) err
199200
}
200201

201202
// Create HTTP request
202-
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/prom/rules/%s", c.rulerAddress, namespace), bytes.NewReader(data))
203+
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s/api/prom/rules/%s", c.rulerAddress, url.PathEscape(namespace)), bytes.NewReader(data))
203204
if err != nil {
204205
return err
205206
}
@@ -223,7 +224,7 @@ func (c *Client) SetRuleGroup(rulegroup rulefmt.RuleGroup, namespace string) err
223224
// DeleteRuleGroup gets the status of an alertmanager instance
224225
func (c *Client) DeleteRuleGroup(namespace string, groupName string) error {
225226
// Create HTTP request
226-
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/prom/rules/%s/%s", c.rulerAddress, namespace, groupName), nil)
227+
req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/api/prom/rules/%s/%s", c.rulerAddress, url.PathEscape(namespace), url.PathEscape(groupName)), nil)
227228
if err != nil {
228229
return err
229230
}

pkg/ruler/api.go

Lines changed: 74 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828

2929
// RegisterRoutes registers the ruler API HTTP routes with the provided Router.
3030
func (r *Ruler) RegisterRoutes(router *mux.Router, middleware middleware.Interface) {
31+
// Routes for this API must be encoded to allow for various characters to be
32+
// present in the path URL
3133
router = router.UseEncodedPath()
3234
for _, route := range []struct {
3335
name, method, path string
@@ -284,14 +286,12 @@ func (r *Ruler) alerts(w http.ResponseWriter, req *http.Request) {
284286
}
285287

286288
var (
287-
// ErrNoNamespace signals the requested namespace does not exist
288-
ErrNoNamespace = errors.New("a namespace must be provided in the url")
289+
// ErrNoNamespace signals that no namespace was specified in the request
290+
ErrNoNamespace = errors.New("a namespace must be provided in the request")
289291
// ErrNoGroupName signals a group name url parameter was not found
290292
ErrNoGroupName = errors.New("a matching group name must be provided in the request")
291293
// ErrNoRuleGroups signals the rule group requested does not exist
292294
ErrNoRuleGroups = errors.New("no rule groups found")
293-
// ErrNoUserID is returned when no user ID is provided
294-
ErrNoUserID = errors.New("no id provided")
295295
// ErrBadRuleGroup is returned when the provided rule group can not be unmarshalled
296296
ErrBadRuleGroup = errors.New("unable to decoded rule group")
297297
)
@@ -319,6 +319,39 @@ func ValidateRuleGroup(g rulefmt.RuleGroup) []error {
319319
return errs
320320
}
321321

322+
func marshalAndSend(formatted interface{}, w http.ResponseWriter, logger log.Logger) {
323+
d, err := yaml.Marshal(&formatted)
324+
if err != nil {
325+
level.Error(logger).Log("msg", "error marshalling yaml rule groups", "err", err)
326+
http.Error(w, err.Error(), http.StatusInternalServerError)
327+
return
328+
}
329+
330+
w.Header().Set("Content-Type", "application/yaml")
331+
if _, err := w.Write(d); err != nil {
332+
level.Error(logger).Log("msg", "error writing yaml response", "err", err)
333+
return
334+
}
335+
}
336+
337+
func respondAccepted(w http.ResponseWriter, logger log.Logger) {
338+
b, err := json.Marshal(&response{
339+
Status: "success",
340+
})
341+
if err != nil {
342+
level.Error(logger).Log("msg", "error marshaling json response", "err", err)
343+
respondError(logger, w, "unable to marshal the requested data")
344+
return
345+
}
346+
w.Header().Set("Content-Type", "application/json")
347+
348+
// Return a status accepted because the rule has been stored and queued for polling, but is not currently active
349+
w.WriteHeader(http.StatusAccepted)
350+
if n, err := w.Write(b); err != nil {
351+
level.Error(logger).Log("msg", "error writing response", "bytesWritten", n, "err", err)
352+
}
353+
}
354+
322355
// parseNamespace parses the namespace from the provided set of params, in this
323356
// api these params are derived from the url path
324357
func parseNamespace(params map[string]string) (string, error) {
@@ -327,7 +360,7 @@ func parseNamespace(params map[string]string) (string, error) {
327360
return "", ErrNoNamespace
328361
}
329362

330-
namespace, err := url.PathUnescape(namespace) // namespaces needs to be unescaped if in the URL
363+
namespace, err := url.PathUnescape(namespace)
331364
if err != nil {
332365
return "", err
333366
}
@@ -343,38 +376,48 @@ func parseGroupName(params map[string]string) (string, error) {
343376
return "", ErrNoGroupName
344377
}
345378

346-
groupName, err := url.PathUnescape(groupName) // groupName needs to be unescaped if in the URL
379+
groupName, err := url.PathUnescape(groupName)
347380
if err != nil {
348381
return "", err
349382
}
350383

351384
return groupName, nil
352385
}
353386

354-
func (r *Ruler) listRules(w http.ResponseWriter, req *http.Request) {
355-
logger := util.WithContext(req.Context(), util.Logger)
356-
userID, err := user.ExtractOrgID(req.Context())
387+
func parseRequest(req *http.Request, requireNamespace, requireGroup bool) (string, string, string, error) {
388+
id, err := user.ExtractOrgID(req.Context())
357389
if err != nil {
358-
http.Error(w, err.Error(), http.StatusBadRequest)
359-
return
390+
return "", "", "", user.ErrNoOrgID
360391
}
361392

362393
vars := mux.Vars(req)
363394

364-
// Parse the namespace from the url path parameters and return
365-
// a 400 if it is invalid and return a full set of rules if no
366-
// namespace is provided
367395
namespace, err := parseNamespace(vars)
368396
if err != nil {
369-
// If a namespace is not provided continue as usual and return a full set of rules
370-
if err != ErrNoNamespace {
371-
http.Error(w, err.Error(), http.StatusBadRequest)
372-
return
397+
if err != ErrNoNamespace || requireNamespace {
398+
return "", "", "", err
373399
}
374-
level.Debug(logger).Log("msg", "retrieving rule groups with namespace", "userID", userID, "namespace", namespace)
375400
}
376401

377-
level.Debug(logger).Log("msg", "retrieving rule groups from rule store", "userID", userID)
402+
group, err := parseGroupName(vars)
403+
if err != nil {
404+
if err != ErrNoGroupName || requireGroup {
405+
return "", "", "", err
406+
}
407+
}
408+
409+
return id, namespace, group, nil
410+
}
411+
412+
func (r *Ruler) listRules(w http.ResponseWriter, req *http.Request) {
413+
logger := util.WithContext(req.Context(), util.Logger)
414+
415+
userID, namespace, _, err := parseRequest(req, false, false)
416+
if err != nil {
417+
respondError(logger, w, err.Error())
418+
}
419+
420+
level.Debug(logger).Log("msg", "retrieving rule groups with namespace", "userID", userID, "namespace", namespace)
378421
rgs, err := r.store.ListRuleGroups(req.Context(), userID, namespace)
379422
if err != nil {
380423
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -390,46 +433,14 @@ func (r *Ruler) listRules(w http.ResponseWriter, req *http.Request) {
390433
}
391434

392435
formatted := rgs.Formatted()
393-
394-
d, err := yaml.Marshal(&formatted)
395-
if err != nil {
396-
level.Error(logger).Log("msg", "error marshalling yaml rule groups", "err", err)
397-
http.Error(w, err.Error(), http.StatusInternalServerError)
398-
return
399-
}
400-
401-
w.Header().Set("Content-Type", "application/yaml")
402-
if _, err := w.Write(d); err != nil {
403-
level.Error(logger).Log("msg", "error writing yaml response", "err", err)
404-
http.Error(w, err.Error(), http.StatusInternalServerError)
405-
return
406-
}
436+
marshalAndSend(formatted, w, logger)
407437
}
408438

409439
func (r *Ruler) getRuleGroup(w http.ResponseWriter, req *http.Request) {
410440
logger := util.WithContext(req.Context(), util.Logger)
411-
userID, err := user.ExtractOrgID(req.Context())
412-
if err != nil {
413-
http.Error(w, err.Error(), http.StatusBadRequest)
414-
return
415-
}
416-
417-
vars := mux.Vars(req)
418-
419-
// Parse the namespace from the url path parameters and return
420-
// a 400 if it is invalid
421-
namespace, err := parseNamespace(vars)
422-
if err != nil {
423-
http.Error(w, err.Error(), http.StatusBadRequest)
424-
return
425-
}
426-
427-
// Parse the rule group name from the url path parameters and return
428-
// a 400 if it is invalid
429-
groupName, err := parseGroupName(vars)
441+
userID, namespace, groupName, err := parseRequest(req, true, true)
430442
if err != nil {
431-
http.Error(w, err.Error(), http.StatusBadRequest)
432-
return
443+
respondError(logger, w, err.Error())
433444
}
434445

435446
rg, err := r.store.GetRuleGroup(req.Context(), userID, namespace, groupName)
@@ -442,37 +453,15 @@ func (r *Ruler) getRuleGroup(w http.ResponseWriter, req *http.Request) {
442453
return
443454
}
444455

445-
formattedRG := store.FromProto(rg)
446-
447-
d, err := yaml.Marshal(&formattedRG)
448-
if err != nil {
449-
level.Error(logger).Log("msg", "error marshalling yaml rule groups", "err", err)
450-
http.Error(w, err.Error(), http.StatusInternalServerError)
451-
return
452-
}
453-
454-
w.Header().Set("Content-Type", "application/yaml")
455-
if _, err := w.Write(d); err != nil {
456-
level.Error(logger).Log("msg", "error writing yaml response", "err", err)
457-
http.Error(w, err.Error(), http.StatusInternalServerError)
458-
return
459-
}
456+
formatted := store.FromProto(rg)
457+
marshalAndSend(formatted, w, logger)
460458
}
461459

462460
func (r *Ruler) createRuleGroup(w http.ResponseWriter, req *http.Request) {
463461
logger := util.WithContext(req.Context(), util.Logger)
464-
userID, err := user.ExtractOrgID(req.Context())
465-
if err != nil {
466-
http.Error(w, err.Error(), http.StatusBadRequest)
467-
return
468-
}
469-
470-
// Parse the namespace from the url path parameters and return
471-
// a 400 if it is invalid
472-
namespace, err := parseNamespace(mux.Vars(req))
462+
userID, namespace, _, err := parseRequest(req, true, false)
473463
if err != nil {
474-
http.Error(w, err.Error(), http.StatusBadRequest)
475-
return
464+
respondError(logger, w, err.Error())
476465
}
477466

478467
payload, err := ioutil.ReadAll(req.Body)
@@ -509,34 +498,15 @@ func (r *Ruler) createRuleGroup(w http.ResponseWriter, req *http.Request) {
509498
return
510499
}
511500

512-
// Return a status accepted because the rule has been stored and queued for polling, but is not currently active
513-
w.WriteHeader(http.StatusAccepted)
501+
respondAccepted(w, logger)
514502
}
515503

516504
func (r *Ruler) deleteRuleGroup(w http.ResponseWriter, req *http.Request) {
517505
logger := util.WithContext(req.Context(), util.Logger)
518-
userID, err := user.ExtractOrgID(req.Context())
519-
if err != nil {
520-
http.Error(w, err.Error(), http.StatusBadRequest)
521-
return
522-
}
523-
524-
vars := mux.Vars(req)
525-
526-
// Parse the namespace from the url path parameters and return
527-
// a 400 if it is invalid
528-
namespace, err := parseNamespace(vars)
529-
if err != nil {
530-
http.Error(w, err.Error(), http.StatusBadRequest)
531-
return
532-
}
533506

534-
// Parse the rule group name from the url path parameters and return
535-
// a 400 if it is invalid
536-
groupName, err := parseGroupName(vars)
507+
userID, namespace, groupName, err := parseRequest(req, true, true)
537508
if err != nil {
538-
http.Error(w, err.Error(), http.StatusBadRequest)
539-
return
509+
respondError(logger, w, err.Error())
540510
}
541511

542512
err = r.store.DeleteRuleGroup(req.Context(), userID, namespace, groupName)
@@ -550,6 +520,5 @@ func (r *Ruler) deleteRuleGroup(w http.ResponseWriter, req *http.Request) {
550520
return
551521
}
552522

553-
// Return a status accepted because the rule has been stored and queued for polling, but is not currently active
554-
w.WriteHeader(http.StatusAccepted)
523+
respondAccepted(w, logger)
555524
}

0 commit comments

Comments
 (0)