@@ -28,6 +28,8 @@ import (
28
28
29
29
// RegisterRoutes registers the ruler API HTTP routes with the provided Router.
30
30
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
31
33
router = router .UseEncodedPath ()
32
34
for _ , route := range []struct {
33
35
name , method , path string
@@ -284,14 +286,12 @@ func (r *Ruler) alerts(w http.ResponseWriter, req *http.Request) {
284
286
}
285
287
286
288
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 " )
289
291
// ErrNoGroupName signals a group name url parameter was not found
290
292
ErrNoGroupName = errors .New ("a matching group name must be provided in the request" )
291
293
// ErrNoRuleGroups signals the rule group requested does not exist
292
294
ErrNoRuleGroups = errors .New ("no rule groups found" )
293
- // ErrNoUserID is returned when no user ID is provided
294
- ErrNoUserID = errors .New ("no id provided" )
295
295
// ErrBadRuleGroup is returned when the provided rule group can not be unmarshalled
296
296
ErrBadRuleGroup = errors .New ("unable to decoded rule group" )
297
297
)
@@ -319,6 +319,39 @@ func ValidateRuleGroup(g rulefmt.RuleGroup) []error {
319
319
return errs
320
320
}
321
321
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
+
322
355
// parseNamespace parses the namespace from the provided set of params, in this
323
356
// api these params are derived from the url path
324
357
func parseNamespace (params map [string ]string ) (string , error ) {
@@ -327,7 +360,7 @@ func parseNamespace(params map[string]string) (string, error) {
327
360
return "" , ErrNoNamespace
328
361
}
329
362
330
- namespace , err := url .PathUnescape (namespace ) // namespaces needs to be unescaped if in the URL
363
+ namespace , err := url .PathUnescape (namespace )
331
364
if err != nil {
332
365
return "" , err
333
366
}
@@ -343,38 +376,48 @@ func parseGroupName(params map[string]string) (string, error) {
343
376
return "" , ErrNoGroupName
344
377
}
345
378
346
- groupName , err := url .PathUnescape (groupName ) // groupName needs to be unescaped if in the URL
379
+ groupName , err := url .PathUnescape (groupName )
347
380
if err != nil {
348
381
return "" , err
349
382
}
350
383
351
384
return groupName , nil
352
385
}
353
386
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 ())
357
389
if err != nil {
358
- http .Error (w , err .Error (), http .StatusBadRequest )
359
- return
390
+ return "" , "" , "" , user .ErrNoOrgID
360
391
}
361
392
362
393
vars := mux .Vars (req )
363
394
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
367
395
namespace , err := parseNamespace (vars )
368
396
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
373
399
}
374
- level .Debug (logger ).Log ("msg" , "retrieving rule groups with namespace" , "userID" , userID , "namespace" , namespace )
375
400
}
376
401
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 )
378
421
rgs , err := r .store .ListRuleGroups (req .Context (), userID , namespace )
379
422
if err != nil {
380
423
http .Error (w , err .Error (), http .StatusBadRequest )
@@ -390,46 +433,14 @@ func (r *Ruler) listRules(w http.ResponseWriter, req *http.Request) {
390
433
}
391
434
392
435
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 )
407
437
}
408
438
409
439
func (r * Ruler ) getRuleGroup (w http.ResponseWriter , req * http.Request ) {
410
440
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 )
430
442
if err != nil {
431
- http .Error (w , err .Error (), http .StatusBadRequest )
432
- return
443
+ respondError (logger , w , err .Error ())
433
444
}
434
445
435
446
rg , err := r .store .GetRuleGroup (req .Context (), userID , namespace , groupName )
@@ -442,37 +453,15 @@ func (r *Ruler) getRuleGroup(w http.ResponseWriter, req *http.Request) {
442
453
return
443
454
}
444
455
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 )
460
458
}
461
459
462
460
func (r * Ruler ) createRuleGroup (w http.ResponseWriter , req * http.Request ) {
463
461
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 )
473
463
if err != nil {
474
- http .Error (w , err .Error (), http .StatusBadRequest )
475
- return
464
+ respondError (logger , w , err .Error ())
476
465
}
477
466
478
467
payload , err := ioutil .ReadAll (req .Body )
@@ -509,34 +498,15 @@ func (r *Ruler) createRuleGroup(w http.ResponseWriter, req *http.Request) {
509
498
return
510
499
}
511
500
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 )
514
502
}
515
503
516
504
func (r * Ruler ) deleteRuleGroup (w http.ResponseWriter , req * http.Request ) {
517
505
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
- }
533
506
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 )
537
508
if err != nil {
538
- http .Error (w , err .Error (), http .StatusBadRequest )
539
- return
509
+ respondError (logger , w , err .Error ())
540
510
}
541
511
542
512
err = r .store .DeleteRuleGroup (req .Context (), userID , namespace , groupName )
@@ -550,6 +520,5 @@ func (r *Ruler) deleteRuleGroup(w http.ResponseWriter, req *http.Request) {
550
520
return
551
521
}
552
522
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 )
555
524
}
0 commit comments