Kaynağa Gözat

preliminary endpoints for slack notifs for incidents

Mohammed Nafees 4 yıl önce
ebeveyn
işleme
43182efdc7

+ 90 - 0
api/server/handlers/cluster/notify_new_incident.go

@@ -0,0 +1,90 @@
+package cluster
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/integrations/slack"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type NotifyNewIncidentHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewNotifyNewIncidentHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *NotifyNewIncidentHandler {
+	return &NotifyNewIncidentHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *NotifyNewIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.IncidentNotifyRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// FIXME: better error detection for correct incident ID
+	segments := strings.Split(request.IncidentID, ":")
+	if len(segments) != 4 || (len(segments) > 0 && segments[0] != "incident") {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("invalid incident ID: %s", request.IncidentID)))
+		return
+	}
+
+	slackInts, _ := c.Repo().SlackIntegration().ListSlackIntegrationsByProjectID(cluster.ProjectID)
+
+	rel, err := c.Repo().Release().ReadRelease(cluster.ID, segments[1], segments[2])
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	var notifConf *types.NotificationConfig
+
+	if rel != nil && rel.NotificationConfig != 0 {
+		conf, err := c.Repo().NotificationConfig().ReadNotificationConfig(rel.NotificationConfig)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		notifConf = conf.ToNotificationConfigType()
+	}
+
+	notifier := slack.NewSlackNotifier(notifConf, slackInts...)
+
+	notifyOpts := &slack.NotifyOpts{
+		ProjectID:   cluster.ProjectID,
+		ClusterID:   cluster.ID,
+		ClusterName: cluster.Name,
+		Name:        segments[1],
+		Namespace:   segments[2],
+		URL: fmt.Sprintf(
+			"%s/cluster-dashboard/incidents/%s?namespace=%s",
+			c.Config().ServerConf.ServerURL,
+			request.IncidentID,
+			segments[2],
+		),
+	}
+
+	if !cluster.NotificationsDisabled {
+		notifier.Notify(notifyOpts)
+	}
+}

+ 90 - 0
api/server/handlers/cluster/notify_resolved_incident.go

@@ -0,0 +1,90 @@
+package cluster
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/integrations/slack"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type NotifyResolvedIncidentHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewNotifyResolvedIncidentHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *NotifyResolvedIncidentHandler {
+	return &NotifyResolvedIncidentHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *NotifyResolvedIncidentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	request := &types.IncidentNotifyRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// FIXME: better error detection for correct incident ID
+	segments := strings.Split(request.IncidentID, ":")
+	if len(segments) != 4 || (len(segments) > 0 && segments[0] != "incident") {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("invalid incident ID: %s", request.IncidentID)))
+		return
+	}
+
+	slackInts, _ := c.Repo().SlackIntegration().ListSlackIntegrationsByProjectID(cluster.ProjectID)
+
+	rel, err := c.Repo().Release().ReadRelease(cluster.ID, segments[1], segments[2])
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	var notifConf *types.NotificationConfig
+
+	if rel != nil && rel.NotificationConfig != 0 {
+		conf, err := c.Repo().NotificationConfig().ReadNotificationConfig(rel.NotificationConfig)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		notifConf = conf.ToNotificationConfigType()
+	}
+
+	notifier := slack.NewSlackNotifier(notifConf, slackInts...)
+
+	notifyOpts := &slack.NotifyOpts{
+		ProjectID:   cluster.ProjectID,
+		ClusterID:   cluster.ID,
+		ClusterName: cluster.Name,
+		Name:        segments[1],
+		Namespace:   segments[2],
+		URL: fmt.Sprintf(
+			"%s/cluster-dashboard/incidents/%s?namespace=%s",
+			c.Config().ServerConf.ServerURL,
+			request.IncidentID,
+			segments[2],
+		),
+	}
+
+	if !cluster.NotificationsDisabled {
+		notifier.Notify(notifyOpts)
+	}
+}

+ 58 - 0
api/server/router/cluster.go

@@ -1004,5 +1004,63 @@ func getClusterRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/incidents/notify_new -> cluster.NewNotifyNewIncidentHandler
+	notifyNewIncidentEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/incidents/notify_new",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	notifyNewIncidentHandler := cluster.NewNotifyNewIncidentHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: notifyNewIncidentEndpoint,
+		Handler:  notifyNewIncidentHandler,
+		Router:   r,
+	})
+
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/incidents/notify_resolved -> cluster.NewNotifyResolvedIncidentHandler
+	notifyResolvedIncidentEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/incidents/notify_resolved",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	notifyResolvedIncidentHandler := cluster.NewNotifyResolvedIncidentHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: notifyResolvedIncidentEndpoint,
+		Handler:  notifyResolvedIncidentHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 4 - 0
api/types/cluster.go

@@ -257,3 +257,7 @@ type GetIncidentsRequest struct {
 type GetIncidentEventLogsRequest struct {
 	LogID string `schema:"log_id"`
 }
+
+type IncidentNotifyRequest struct {
+	IncidentID string `json:"incident_id" form:"required"`
+}