prometheus_incoming.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package webhook
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "connectrpc.com/connect"
  7. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  8. "github.com/porter-dev/porter/api/server/authz"
  9. "github.com/porter-dev/porter/api/server/handlers"
  10. "github.com/porter-dev/porter/api/server/shared"
  11. "github.com/porter-dev/porter/api/server/shared/apierrors"
  12. "github.com/porter-dev/porter/api/server/shared/config"
  13. "github.com/porter-dev/porter/api/server/shared/requestutils"
  14. "github.com/porter-dev/porter/api/types"
  15. "github.com/porter-dev/porter/internal/telemetry"
  16. )
  17. // PrometheusAlertWebhookHandler handles incoming prometheus alerts
  18. type PrometheusAlertWebhookHandler struct {
  19. handlers.PorterHandlerReadWriter
  20. authz.KubernetesAgentGetter
  21. }
  22. // NewPrometheusAlertWebhookHandler returns an instance of PrometheusAlertWebhookHandler
  23. func NewPrometheusAlertWebhookHandler(
  24. config *config.Config,
  25. decoderValidator shared.RequestDecoderValidator,
  26. writer shared.ResultWriter,
  27. ) *PrometheusAlertWebhookHandler {
  28. return &PrometheusAlertWebhookHandler{
  29. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  30. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  31. }
  32. }
  33. func (p *PrometheusAlertWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. ctx, span := telemetry.NewSpan(r.Context(), "serve-post-prometheus-alert")
  35. defer span.End()
  36. // get the webhook id from the request
  37. projectID, err := requestutils.GetURLParamUint(r, types.URLParamProjectID)
  38. if err != nil {
  39. e := telemetry.Error(ctx, span, err, "error getting project ID")
  40. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
  41. return
  42. }
  43. clusterID, err := requestutils.GetURLParamUint(r, types.URLParamClusterID)
  44. if err != nil {
  45. e := telemetry.Error(ctx, span, nil, "error getting cluster ID")
  46. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
  47. return
  48. }
  49. prometheusAlert := &types.PrometheusAlert{}
  50. if ok := p.DecodeAndValidate(w, r, prometheusAlert); !ok {
  51. e := telemetry.Error(ctx, span, nil, "error decoding request")
  52. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusBadRequest))
  53. return
  54. }
  55. if err := p.handlePrometheusAlert(ctx, int64(projectID), int64(clusterID), prometheusAlert); err != nil {
  56. e := telemetry.Error(ctx, span, err, "error handling prometheus alert")
  57. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(e, http.StatusInternalServerError))
  58. return
  59. }
  60. p.WriteResult(w, r, "")
  61. }
  62. func (p *PrometheusAlertWebhookHandler) handlePrometheusAlert(ctx context.Context, projectId, clusterId int64, prometheusAlert *types.PrometheusAlert) error {
  63. ctx, span := telemetry.NewSpan(ctx, "porter-process-prom-alert")
  64. defer span.End()
  65. recordPrometheusAlertRequest := connect.NewRequest(&porterv1.RecordPrometheusAlertRequest{
  66. ProjectId: projectId,
  67. ClusterId: clusterId,
  68. })
  69. labelKeyValues := ""
  70. for _, alert := range prometheusAlert.Alerts {
  71. for k, v := range alert.Labels {
  72. labelKeyValues += fmt.Sprintf("%s %s", k, v)
  73. }
  74. if alert.Labels["alertname"] == "NoopAlert" {
  75. continue
  76. }
  77. recordPrometheusAlertRequest.Msg.Alerts = append(recordPrometheusAlertRequest.Msg.Alerts, &porterv1.Alert{
  78. Name: alert.Labels["name"],
  79. Namespace: alert.Labels["namespace"],
  80. Type: p.getType(alert),
  81. })
  82. }
  83. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "porter-app-alert-labels", Value: labelKeyValues})
  84. _, err := p.Config().ClusterControlPlaneClient.RecordPrometheusAlert(ctx, recordPrometheusAlertRequest)
  85. if err != nil {
  86. return telemetry.Error(ctx, span, err, "error recording prometheus alert")
  87. }
  88. return nil
  89. }
  90. func (p *PrometheusAlertWebhookHandler) getType(alert types.Alert) porterv1.InvolvedObjectType {
  91. switch alert.Labels["involvedObjectType"] {
  92. case "Deployment":
  93. return porterv1.InvolvedObjectType_INVOLVED_OBJECT_TYPE_DEPLOYMENT
  94. case "StatefulSet":
  95. return porterv1.InvolvedObjectType_INVOLVED_OBJECT_TYPE_STATEFULSET
  96. case "DaemonSet":
  97. return porterv1.InvolvedObjectType_INVOLVED_OBJECT_TYPE_DAEMONSET
  98. default:
  99. return porterv1.InvolvedObjectType_INVOLVED_OBJECT_TYPE_UNSPECIFIED
  100. }
  101. }