diff --git a/ee/drift/controllers/drift.go b/ee/drift/controllers/drift.go index f7bc2fff2..eed5a6839 100644 --- a/ee/drift/controllers/drift.go +++ b/ee/drift/controllers/drift.go @@ -243,7 +243,7 @@ func (mc MainController) ProcessAllDrift(c *gin.Context) { // Get the status code statusCode := resp.StatusCode if statusCode != 200 { - log.Printf("got unexpected drift status for project: %v - status: %v", orgSetting.OrgID, statusCode) + log.Printf("got unexpected drift status for org: %v - status: %v", orgSetting.OrgID, statusCode) } } } diff --git a/ee/drift/controllers/notifications.go b/ee/drift/controllers/notifications.go index 95804ffe1..b8a3db54f 100644 --- a/ee/drift/controllers/notifications.go +++ b/ee/drift/controllers/notifications.go @@ -5,9 +5,15 @@ import ( "encoding/json" "fmt" "github.com/diggerhq/digger/ee/drift/dbmodels" + "github.com/diggerhq/digger/ee/drift/model" + utils2 "github.com/diggerhq/digger/next/utils" "github.com/gin-gonic/gin" + "github.com/slack-go/slack" "log" "net/http" + "net/url" + "os" + "time" ) func sendTestSlackWebhook(webhookURL string) error { @@ -94,3 +100,179 @@ func (mc MainController) SendTestSlackNotificationForOrg(c *gin.Context) { c.String(200, "ok") } + +func sectionBlockForProject(project model.Project) (*slack.SectionBlock, error) { + switch project.DriftStatus { + case dbmodels.DriftStatusNoDrift: + sectionBlock := slack.NewSectionBlock( + nil, + []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("", project.ID, project.Name), false, false), + slack.NewTextBlockObject("mrkdwn", ":large_green_circle: No Drift", false, false), + }, + nil, + ) + return sectionBlock, nil + case dbmodels.DriftStatusAcknowledgeDrift: + sectionBlock := slack.NewSectionBlock( + nil, + []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("", project.ID, project.Name), false, false), + slack.NewTextBlockObject("mrkdwn", ":white_circle: Acknowledged Drift", false, false), + }, + nil, + ) + return sectionBlock, nil + case dbmodels.DriftStatusNewDrift: + sectionBlock := slack.NewSectionBlock( + nil, + []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("", project.ID, project.Name), false, false), + slack.NewTextBlockObject("mrkdwn", ":white_circle: New Drift", false, false), + }, + nil, + ) + return sectionBlock, nil + default: + return nil, fmt.Errorf("Could not") + } +} + +type RealSlackNotificationForOrgRequest struct { + OrgId string `json:"org_id"` +} + +func (mc MainController) SendRealSlackNotificationForOrg(c *gin.Context) { + var request RealSlackNotificationForOrgRequest + err := c.BindJSON(&request) + if err != nil { + log.Printf("Error binding JSON: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error binding JSON"}) + return + } + orgId := request.OrgId + + os := dbmodels.DB.Query.OrgSetting + orgSettings, err := dbmodels.DB.Query.OrgSetting.Where(os.OrgID.Eq(orgId)).First() + + slackNotificationUrl := orgSettings.SlackNotificationURL + + projects, err := dbmodels.DB.LoadProjectsForOrg(orgId) + if err != nil { + log.Printf("could not load projects for org %v err: %v", orgId, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not load projects for org " + orgId}) + return + } + + numOfProjectsWithDriftEnabled := 0 + var messageBlocks []slack.Block + fieldsBlock := slack.NewSectionBlock( + nil, + []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", "*Project*", false, false), + slack.NewTextBlockObject("mrkdwn", "*Status*", false, false), + }, + nil, + ) + messageBlocks = append(messageBlocks, fieldsBlock) + messageBlocks = append(messageBlocks, slack.NewDividerBlock()) + for _, project := range projects { + if project.DriftEnabled { + numOfProjectsWithDriftEnabled++ + sectionBlockForProject, err := sectionBlockForProject(*project) + if err != nil { + log.Printf("could not get block for project: %v err: %v", project.ID, err) + c.JSON(500, gin.H{"error": fmt.Sprintf("could not get notification block for project %v", project.ID)}) + return + } + messageBlocks = append(messageBlocks, sectionBlockForProject) + messageBlocks = append(messageBlocks, slack.NewDividerBlock()) + } + } + + if numOfProjectsWithDriftEnabled == 0 { + log.Printf("no projects with drift enabled for org: %v, succeeding", orgId) + c.String(200, "ok") + return + } + + msg := &slack.WebhookMessage{ + Blocks: &slack.Blocks{ + BlockSet: messageBlocks, + }, + } + + err = slack.PostWebhook(slackNotificationUrl, msg) + if err != nil { + log.Printf("error sending slack webhook: %v", err) + c.JSON(500, gin.H{"error": "error sending slack webhook"}) + return + } + + c.String(200, "ok") +} + +func (mc MainController) ProcessAllNotifications(c *gin.Context) { + diggerHostname := os.Getenv("DIGGER_HOSTNAME") + webhookSecret := os.Getenv("DIGGER_WEBHOOK_SECRET") + orgSettings, err := dbmodels.DB.Query.OrgSetting.Find() + if err != nil { + log.Printf("could not select all orgs: %v", err) + } + + sendSlackNotificationUrl, err := url.JoinPath(diggerHostname, "_internal/send_slack_notification_for_org") + if err != nil { + log.Printf("could not form drift url: %v", err) + c.JSON(500, gin.H{"error": "could not form drift url"}) + return + } + + for _, orgSetting := range orgSettings { + cron := orgSetting.Schedule + matches, err := utils2.MatchesCrontab(cron, time.Now().Add(-15*time.Minute)) + if err != nil { + log.Printf("could not check matching crontab for org :%v", orgSetting.OrgID) + c.String(500, "could not check matching crontab for org :%v", orgSetting.OrgID) + return + } + + if matches { + payload := RealSlackNotificationForOrgRequest{OrgId: orgSetting.OrgID} + + // Convert payload to JSON + jsonPayload, err := json.Marshal(payload) + if err != nil { + fmt.Println("Process Drift: error marshaling JSON:", err) + return + } + + // Create a new request + req, err := http.NewRequest("POST", sendSlackNotificationUrl, bytes.NewBuffer(jsonPayload)) + if err != nil { + fmt.Println("Process slack notification: Error creating request:", err) + return + } + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", webhookSecret)) + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return + } + defer resp.Body.Close() + + // Get the status code + statusCode := resp.StatusCode + if statusCode != 200 { + log.Printf("send slack notification got unexpected status for org: %v - status: %v", orgSetting.OrgID, statusCode) + } + } + } + + c.String(200, "success") +} diff --git a/ee/drift/go.mod b/ee/drift/go.mod index d7023aec0..2365ff569 100644 --- a/ee/drift/go.mod +++ b/ee/drift/go.mod @@ -1,6 +1,7 @@ module github.com/diggerhq/digger/ee/drift replace github.com/diggerhq/digger/next => ../../next + replace github.com/diggerhq/digger/libs => ../../libs go 1.22.4 @@ -127,6 +128,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect github.com/gruntwork-io/go-commons v0.17.1 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect @@ -213,6 +215,7 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/samber/lo v1.46.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/slack-go/slack v0.10.3 // indirect github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d // indirect github.com/sourcegraph/jsonrpc2 v0.2.0 // indirect github.com/spf13/afero v1.11.0 // indirect @@ -238,11 +241,11 @@ require ( go.mozilla.org/sops/v3 v3.7.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect - go.opentelemetry.io/otel v1.19.0 // indirect - go.opentelemetry.io/otel/metric v1.19.0 // indirect - go.opentelemetry.io/otel/sdk v1.19.0 // indirect - go.opentelemetry.io/otel/trace v1.19.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.24.0 // indirect diff --git a/ee/drift/go.sum b/ee/drift/go.sum index dac03569c..85663aeb1 100644 --- a/ee/drift/go.sum +++ b/ee/drift/go.sum @@ -563,6 +563,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -718,6 +719,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= @@ -1131,6 +1133,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw= +github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1252,16 +1256,19 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= diff --git a/ee/drift/main.go b/ee/drift/main.go index 7aef6019e..81a3ad926 100644 --- a/ee/drift/main.go +++ b/ee/drift/main.go @@ -76,8 +76,11 @@ func main() { r.POST("/repos/:repo/projects/:projectName/jobs/:jobId/set-status", middleware.JobTokenAuth(), controller.SetJobStatusForProject) + r.POST("/_internal/process_notifications", middleware.WebhookAuth(), controller.ProcessAllNotifications) + r.POST("/_internal/send_slack_notification_for_org", middleware.WebhookAuth(), controller.SendRealSlackNotificationForOrg) r.POST("/_internal/send_test_slack_notification_for_org", middleware.WebhookAuth(), controller.SendTestSlackNotificationForOrg) - r.POST("/_internal/process_drift", middleware.WebhookAuth(), controller.ProcessAllDrift) + + r.POST("/_internal/process_drift", middleware.WebhookAuth(), controller.ProcessAllDrift) r.POST("/_internal/process_drift_for_org", middleware.WebhookAuth(), controller.ProcessDriftForOrg) r.POST("/_internal/trigger_drift_for_project", middleware.WebhookAuth(), controller.TriggerDriftRunForProject)