Просмотр исходного кода

integrate slack notifications for helm upgrades

Alexander Belanger 4 лет назад
Родитель
Сommit
1986e9a3ea
2 измененных файлов с 193 добавлено и 1 удалено
  1. 137 0
      internal/integrations/slack/notifier.go
  2. 56 1
      server/api/release_handler.go

+ 137 - 0
internal/integrations/slack/notifier.go

@@ -0,0 +1,137 @@
+package slack
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/porter-dev/porter/internal/models/integrations"
+)
+
+type Notifier interface {
+	Notify(opts *NotifyOpts) error
+}
+
+type DeploymentStatus string
+
+const (
+	StatusDeployed string = "deployed"
+	StatusFailed   string = "failed"
+)
+
+type NotifyOpts struct {
+	// ProjectID is the id of the Porter project that this deployment belongs to
+	ProjectID uint
+
+	// ClusterID is the id of the Porter cluster that this deployment belongs to
+	ClusterID uint
+
+	// ClusterName is the name of the cluster that this deployment was deployed in
+	ClusterName string
+
+	// Status is the current status of the deployment.
+	Status string
+
+	// Info is any additional information about this status, such as an error message if
+	// the deployment failed.
+	Info string
+
+	// Name is the name of the deployment that this notification refers to.
+	Name string
+
+	// Namespace is the Kubernetes namespace of the deployment that this notification refers to.
+	Namespace string
+
+	URL string
+
+	Version int
+}
+
+type SlackNotifier struct {
+	slackInts []*integrations.SlackIntegration
+}
+
+func NewSlackNotifier(slackInts ...*integrations.SlackIntegration) Notifier {
+	return &SlackNotifier{
+		slackInts: slackInts,
+	}
+}
+
+func (s *SlackNotifier) Notify(opts *NotifyOpts) error {
+	var statusPayload string
+
+	switch opts.Status {
+	case StatusDeployed:
+		statusPayload = getSuccessPayload(opts)
+	case StatusFailed:
+		statusPayload = getFailedPayload(opts)
+	}
+
+	payload := fmt.Sprintf(`
+	{
+		"blocks": [
+			%s
+			{
+				"type": "divider"
+			},
+			{
+				"type": "section",
+				"text": {
+					"type": "mrkdwn",
+					"text": "*Name:* %s"
+				}
+			},
+			{
+				"type": "section",
+				"text": {
+					"type": "mrkdwn",
+					"text": "*Namespace:* %s"
+				}
+			},
+			{
+				"type": "section",
+				"text": {
+					"type": "mrkdwn",
+					"text": "*Version:* %d"
+				}
+			}
+		]
+	}
+	`, statusPayload, "`"+opts.Name+"`", "`"+opts.Namespace+"`", opts.Version)
+
+	reqBody := bytes.NewReader([]byte(payload))
+	client := &http.Client{
+		Timeout: time.Second * 5,
+	}
+
+	for _, slackInt := range s.slackInts {
+		client.Post(string(slackInt.Webhook), "application/json", reqBody)
+	}
+
+	return nil
+}
+
+func getSuccessPayload(opts *NotifyOpts) string {
+	return fmt.Sprintf(`
+		{
+			"type": "section",
+			"text": {
+				"type": "mrkdwn",
+				"text": ":rocket: Your application %s was successfully updated on Porter! <%s|View the new release.>"
+			}
+		},
+	`, "`"+opts.Name+"`", opts.URL)
+}
+
+func getFailedPayload(opts *NotifyOpts) string {
+	return fmt.Sprintf(`
+		{
+			"type": "section",
+			"text": {
+				"type": "mrkdwn",
+				"text": ":x: Your application %s failed to deploy on Porter. <%s|View the status here.>"
+			}
+		},
+	`, "`"+opts.Name+"`", opts.URL)
+}

+ 56 - 1
server/api/release_handler.go

@@ -25,6 +25,7 @@ import (
 	"github.com/porter-dev/porter/internal/helm/grapher"
 	"github.com/porter-dev/porter/internal/helm/loader"
 	"github.com/porter-dev/porter/internal/integrations/ci/actions"
+	"github.com/porter-dev/porter/internal/integrations/slack"
 	"github.com/porter-dev/porter/internal/kubernetes"
 	"github.com/porter-dev/porter/internal/repository"
 	"gopkg.in/yaml.v2"
@@ -977,9 +978,31 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
+	slackInts, _ := app.Repo.SlackIntegration.ListSlackIntegrationsByProjectID(uint(projID))
+	notifier := slack.NewSlackNotifier(slackInts...)
+
+	notifyOpts := &slack.NotifyOpts{
+		ProjectID:   uint(projID),
+		ClusterID:   form.Cluster.ID,
+		ClusterName: form.Cluster.Name,
+		Name:        name,
+		Namespace:   form.Namespace,
+		URL: fmt.Sprintf(
+			"%s/applications/%s/%s/%s",
+			app.ServerConf.ServerURL,
+			url.PathEscape(form.Cluster.Name),
+			form.Namespace,
+			name,
+		) + fmt.Sprintf("?project_id=%d", uint(projID)),
+	}
+
 	rel, err := agent.UpgradeRelease(conf, form.Values, app.DOConf)
 
 	if err != nil {
+		notifyOpts.Status = slack.StatusFailed
+
+		notifier.Notify(notifyOpts)
+
 		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
 			Code:   ErrReleaseDeploy,
 			Errors: []string{err.Error()},
@@ -988,6 +1011,11 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	notifyOpts.Status = string(rel.Info.Status)
+	notifyOpts.Version = rel.Version
+
+	notifier.Notify(notifyOpts)
+
 	// update the github actions env if the release exists and is built from source
 	if cName := rel.Chart.Metadata.Name; cName == "job" || cName == "web" || cName == "worker" {
 		clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
@@ -1171,9 +1199,31 @@ func (app *App) HandleReleaseDeployWebhook(w http.ResponseWriter, r *http.Reques
 		Values:     rel.Config,
 	}
 
-	_, err = agent.UpgradeReleaseByValues(conf, app.DOConf)
+	slackInts, _ := app.Repo.SlackIntegration.ListSlackIntegrationsByProjectID(uint(form.ReleaseForm.Cluster.ProjectID))
+	notifier := slack.NewSlackNotifier(slackInts...)
+
+	notifyOpts := &slack.NotifyOpts{
+		ProjectID:   uint(form.ReleaseForm.Cluster.ProjectID),
+		ClusterID:   form.Cluster.ID,
+		ClusterName: form.Cluster.Name,
+		Name:        rel.Name,
+		Namespace:   rel.Namespace,
+		URL: fmt.Sprintf(
+			"%s/applications/%s/%s/%s",
+			app.ServerConf.ServerURL,
+			url.PathEscape(form.Cluster.Name),
+			form.Namespace,
+			rel.Name,
+		) + fmt.Sprintf("?project_id=%d", uint(form.ReleaseForm.Cluster.ProjectID)),
+	}
+
+	rel, err = agent.UpgradeReleaseByValues(conf, app.DOConf)
 
 	if err != nil {
+		notifyOpts.Status = slack.StatusFailed
+
+		notifier.Notify(notifyOpts)
+
 		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
 			Code:   ErrReleaseDeploy,
 			Errors: []string{err.Error()},
@@ -1182,6 +1232,11 @@ func (app *App) HandleReleaseDeployWebhook(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
+	notifyOpts.Status = string(rel.Info.Status)
+	notifyOpts.Version = rel.Version
+
+	notifier.Notify(notifyOpts)
+
 	app.analyticsClient.Track(analytics.CreateSegmentRedeployViaWebhookTrack("anonymous", repository.(string)))
 
 	w.WriteHeader(http.StatusOK)