From dd13a6dcebe4a4593b182bf1cbde81e9addef9e2 Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Tue, 26 Oct 2021 21:02:03 +1100
Subject: [PATCH 01/17] feat: rewrite logs2x services
---
services/logs2notifications/Dockerfile | 38 +++
services/logs2notifications/go.mod | 20 ++
services/logs2notifications/go.sum | 187 +++++++++++++
.../internal/handler/main.go | 225 ++++++++++++++++
.../internal/handler/main_test.go | 18 ++
.../internal/handler/rocketchat_events.go | 227 ++++++++++++++++
.../internal/handler/slack_events.go | 64 +++++
.../_lgraphql/projectNotifications.graphql | 41 +++
.../internal/lagoon/client/client.go | 93 +++++++
.../internal/lagoon/client/client_test.go | 81 ++++++
.../internal/lagoon/client/helper_test.go | 6 +
.../lagoon/client/lgraphql/lgraphql.go | 247 ++++++++++++++++++
.../internal/lagoon/client/query.go | 25 ++
.../internal/lagoon/jwt/jwt.go | 30 +++
.../internal/lagoon/project.go | 20 ++
.../internal/schema/notifications.go | 107 ++++++++
.../internal/schema/project.go | 8 +
services/logs2notifications/main.go | 185 +++++++++++++
18 files changed, 1622 insertions(+)
create mode 100644 services/logs2notifications/Dockerfile
create mode 100644 services/logs2notifications/go.mod
create mode 100644 services/logs2notifications/go.sum
create mode 100644 services/logs2notifications/internal/handler/main.go
create mode 100644 services/logs2notifications/internal/handler/main_test.go
create mode 100644 services/logs2notifications/internal/handler/rocketchat_events.go
create mode 100644 services/logs2notifications/internal/handler/slack_events.go
create mode 100644 services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
create mode 100644 services/logs2notifications/internal/lagoon/client/client.go
create mode 100644 services/logs2notifications/internal/lagoon/client/client_test.go
create mode 100644 services/logs2notifications/internal/lagoon/client/helper_test.go
create mode 100644 services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
create mode 100644 services/logs2notifications/internal/lagoon/client/query.go
create mode 100644 services/logs2notifications/internal/lagoon/jwt/jwt.go
create mode 100644 services/logs2notifications/internal/lagoon/project.go
create mode 100644 services/logs2notifications/internal/schema/notifications.go
create mode 100644 services/logs2notifications/internal/schema/project.go
create mode 100644 services/logs2notifications/main.go
diff --git a/services/logs2notifications/Dockerfile b/services/logs2notifications/Dockerfile
new file mode 100644
index 0000000000..34ef5e9920
--- /dev/null
+++ b/services/logs2notifications/Dockerfile
@@ -0,0 +1,38 @@
+# build the binary
+ARG GO_VERSION
+ARG UPSTREAM_REPO
+ARG UPSTREAM_TAG
+FROM golang:${GO_VERSION:-1.13.8} AS builder
+# bring in all the packages
+COPY . /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
+WORKDIR /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
+
+# tests currently don't work because mocking rabbit is interesting
+RUN GO111MODULE=on go test ./...
+# compile
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o logs2notifications .
+
+# put the binary into container
+# use the commons image to get entrypoints
+FROM ${UPSTREAM_REPO:-uselagoon}/commons:${UPSTREAM_TAG:-latest}
+
+ARG LAGOON_VERSION
+ENV LAGOON_VERSION=$LAGOON_VERSION
+
+WORKDIR /app/
+
+# bring the auto-idler binary from the builder
+COPY --from=builder /go/src/github.com/uselagoon/lagoon/services/logs2notifications/logs2notifications .
+
+ENV LAGOON=logs2notifications
+# set defaults
+ENV JWT_SECRET=super-secret-string \
+ JWT_AUDIENCE=api.dev \
+ GRAPHQL_ENDPOINT="http://api:3000/graphql" \
+ RABBITMQ_ADDRESS=broker \
+ RABBITMQ_PORT=5672 \
+ RABBITMQ_USERNAME=guest \
+ RABBITMQ_PASSWORD=guest
+
+ENTRYPOINT ["/sbin/tini", "--", "/lagoon/entrypoints.sh"]
+CMD ["/app/logs2notifications"]
\ No newline at end of file
diff --git a/services/logs2notifications/go.mod b/services/logs2notifications/go.mod
new file mode 100644
index 0000000000..06e5305c9a
--- /dev/null
+++ b/services/logs2notifications/go.mod
@@ -0,0 +1,20 @@
+module github.com/uselagoon/lagoon/services/logs2notifications
+
+go 1.14
+
+require (
+ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
+ github.com/cheshir/go-mq v1.0.2
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/fsouza/go-dockerclient v1.7.3 // indirect
+ github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225
+ github.com/matryer/is v1.4.0 // indirect
+ github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2
+ github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+)
+
+// Fixes for AppID
+replace github.com/cheshir/go-mq v1.0.2 => github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead
+
+replace github.com/NeowayLabs/wabbit v0.0.0-20200409220312-12e68ab5b0c6 => github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204
\ No newline at end of file
diff --git a/services/logs2notifications/go.sum b/services/logs2notifications/go.sum
new file mode 100644
index 0000000000..bcac1aac42
--- /dev/null
+++ b/services/logs2notifications/go.sum
@@ -0,0 +1,187 @@
+bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
+github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
+github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
+github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
+github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
+github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
+github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
+github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
+github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
+github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/fsouza/go-dockerclient v1.7.3/go.mod h1:8xfZB8o9SptLNJ13VoV5pMiRbZGWkU/Omu5VOu/KC9Y=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225 h1:guHWmqIKr4G+gQ4uYU5vcZjsUhhklRA2uOcGVfcfqis=
+github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
+github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
+github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
+github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead h1:brBqfI3SWHkBhydQ4zdYbeQj/4Rq68GHO+Me8W7Fji8=
+github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead/go.mod h1:lAwW/xhfO27t6WSVHFtLdgYioymwJvQxMSH19z00/BY=
+github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204 h1:jXml7E4X/d9v6LATMXFPCF1yW6TKrs+o5wMtYTaBdTw=
+github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204/go.mod h1:l7t6U3j3uZUYroWctp1FvWEktRMuGqx2zCcb5cd8cS8=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71 h1:2MR0pKUzlP3SGgj5NYJe/zRYDwOu9ku6YHy+Iw7l5DM=
+github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218/go.mod h1:GQei++1WClbEC7AN1B9ipY1jCjzllM/7UNg0okAh/Z4=
+github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/uselagoon/lagoon v1.15.1 h1:/EUJJrWmmNrFVIh4vYm71/+fk2yXlCk+M5g8L+2ll0c=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
new file mode 100644
index 0000000000..9ccbd7cd1a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/main.go
@@ -0,0 +1,225 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "time"
+
+ "github.com/cheshir/go-mq"
+ "github.com/matryer/try"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon"
+ lclient "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/jwt"
+ // "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// RabbitBroker .
+type RabbitBroker struct {
+ Hostname string `json:"hostname"`
+ Port string `json:"port"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ QueueName string `json:"queueName"`
+ ExchangeName string `json:"exchangeName"`
+}
+
+// LagoonAPI .
+type LagoonAPI struct {
+ Endpoint string `json:"endpoint"`
+ JWTAudience string `json:"audience"`
+ TokenSigningKey string `json:"tokenSigningKey`
+ JWTSubject string `json:"subject"`
+ JWTIssuer string `json:"issuer"`
+}
+
+// Action is the structure of an action that is received via the message queue.
+type Action struct {
+ Type string `json:"type"` // defines the action type
+ EventType string `json:"eventType"` // defines the eventtype field in the event notification
+ Data map[string]interface{} `json:"data"` // contains the payload for the action, this could be any json so using a map
+}
+
+type messaging interface {
+ Consumer()
+ Publish(string, []byte)
+}
+
+// Messaging is used for the config and client information for the messaging queue.
+type Messaging struct {
+ Config mq.Config
+ LagoonAPI LagoonAPI
+ ConnectionAttempts int
+ ConnectionRetryInterval int
+ EnableDebug bool
+ LagoonAppID string
+}
+
+// Notification .
+type Notification struct {
+ Severity string `json:"severity"`
+ Project string `json:"project"`
+ UUID string `json:"uuid"`
+ Event string `json:"event"`
+ Meta struct {
+ User struct {
+ ID string `json:"id"`
+ PreferredUsername string `json:"preferred_username"`
+ Email string `json:"email"`
+ } `json:"user"`
+ Headers struct {
+ UserAgent string `json:"user-agent"`
+ ContentType string `json:"content-type"`
+ ContentLength string `json:"content-length"`
+ Host string `json:"host"`
+ IPAddress string `json:"ipAddress"`
+ } `json:"headers"`
+ Project string `json:"project"`
+ ProjectName string `json:"projectName"`
+ BranchName string `json:"branchName`
+ Event string `json:"event"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+ Timestamp string `json:"timestamp"`
+ ShortSha string `json:"shortSha"`
+ BuildName string `json:"buildName"`
+ CommitURL string `json:"commitUrl"`
+ Environment string `json:"environment"`
+ EnvironmentID string `json:"environmentId"`
+ EnvironmentName string `json:"environmentName"`
+ Error string `json:"error"`
+ JobName string `json:"jobName"`
+ LogLink string `json:"logLink"`
+ Name string `json:"name"`
+ OpenshiftProject string `json:"openshiftProject"`
+ PromoteSourceEnvironment string `json:"promoteSourceEnvironment"`
+ PullrequestNumber string `json:"pullrequestNumber"`
+ PullrequestTitle string `json:"pullrequestTitle"`
+ PullrequestURL string `json:"pullrequestUrl"`
+ RemoteID string `json:"remoteId"`
+ RepoFullName string `json:"repoFullName"`
+ RepoName string `json:"repoName"`
+ RepoURL string `json:"repoUrl"`
+ Route string `json:"route"`
+ Routes string `json:"routes"`
+ Task string `json:"task"`
+ } `json:"meta"`
+ Message string `json:"message"`
+}
+
+// NewMessaging returns a messaging with config
+func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, startupInterval int, enableDebug bool, appID string) *Messaging {
+ return &Messaging{
+ Config: config,
+ LagoonAPI: lagoonAPI,
+ ConnectionAttempts: startupAttempts,
+ ConnectionRetryInterval: startupInterval,
+ EnableDebug: enableDebug,
+ LagoonAppID: appID,
+ }
+}
+
+// Consumer handles consuming messages sent to the queue that this action handler is connected to and processes them accordingly
+func (h *Messaging) Consumer() {
+ ctx := context.TODO()
+
+ var messageQueue mq.MQ
+ // if no mq is found when the goroutine starts, retry a few times before exiting
+ // default is 10 retry with 30 second delay = 5 minutes
+ err := try.Do(func(attempt int) (bool, error) {
+ var err error
+ messageQueue, err = mq.New(h.Config)
+ if err != nil {
+ log.Println(err,
+ fmt.Sprintf(
+ "Failed to initialize message queue manager, retrying in %d seconds, attempt %d/%d",
+ h.ConnectionRetryInterval,
+ attempt,
+ h.ConnectionAttempts,
+ ),
+ )
+ time.Sleep(time.Duration(h.ConnectionRetryInterval) * time.Second)
+ }
+ return attempt < h.ConnectionAttempts, err
+ })
+ if err != nil {
+ log.Fatalf("Finally failed to initialize message queue manager: %v", err)
+ }
+ defer messageQueue.Close()
+
+ go func() {
+ for err := range messageQueue.Error() {
+ log.Println(fmt.Sprintf("Caught error from message queue: %v", err))
+ }
+ }()
+
+ forever := make(chan bool)
+
+ // Handle any tasks that go to the queue
+ log.Println("Listening for messages in queue lagoon-logs:notifications")
+ err = messageQueue.SetConsumerHandler("notifications-queue", func(message mq.Message) {
+ notification := &Notification{}
+ json.Unmarshal(message.Body(), notification)
+ switch notification.Event {
+ // check if this a `deployEnvironmentLatest` type of action
+ // and perform the steps to run the mutation against the lagoon api
+ case "api:unknownEvent":
+ default:
+ // marshal unmarshal the data into the input we need to use when talking to the lagoon api
+ token, err := jwt.OneMinuteAdminToken(h.LagoonAPI.TokenSigningKey, h.LagoonAPI.JWTAudience, h.LagoonAPI.JWTSubject, h.LagoonAPI.JWTIssuer)
+ if err != nil {
+ // the token wasn't generated
+ if h.EnableDebug {
+ log.Println(err)
+ }
+ break
+ }
+ // get all notifications for said project
+ l := lclient.New(h.LagoonAPI.Endpoint, token, "logs2notifications", false)
+ projectNotifications, err := lagoon.NotificationsForProject(ctx, notification.Project, l)
+ if err != nil {
+ log.Println(err)
+ break
+ }
+ // if len(projectNotifications.Notifications.Slack) > 0 {
+ // fmt.Println(projectNotifications.Notifications.Slack)
+ // }
+ if len(projectNotifications.Notifications.RocketChat) > 0 {
+ for _, rc := range projectNotifications.Notifications.RocketChat {
+ SendToRocketChat(notification, rc.Channel, rc.Webhook, h.LagoonAppID)
+ }
+ }
+ // if len(projectNotifications.Notifications.Email) > 0 {
+ // fmt.Println(projectNotifications.Notifications.Email)
+ // }
+ // if len(projectNotifications.Notifications.MicrosoftTeams) > 0 {
+ // fmt.Println(projectNotifications.Notifications.MicrosoftTeams)
+ // }
+ // if len(projectNotifications.Notifications.Webhook) > 0 {
+ // fmt.Println(projectNotifications.Notifications.Webhook)
+ // }
+ }
+ message.Ack(false) // ack to remove from queue
+ })
+ if err != nil {
+ log.Println(fmt.Sprintf("Failed to set handler to consumer `%s`: %v", "items-queue", err))
+ }
+ <-forever
+}
+
+// toLagoonLogs sends logs to the lagoon-logs message queue
+func (h *Messaging) toLagoonLogs(messageQueue mq.MQ, message map[string]interface{}) {
+ msgBytes, err := json.Marshal(message)
+ if err != nil {
+ if h.EnableDebug {
+ log.Println(err, "Unable to encode message as JSON")
+ }
+ }
+ producer, err := messageQueue.AsyncProducer("lagoon-logs")
+ if err != nil {
+ log.Println(fmt.Sprintf("Failed to get async producer: %v", err))
+ return
+ }
+ producer.Produce(msgBytes)
+}
diff --git a/services/logs2notifications/internal/handler/main_test.go b/services/logs2notifications/internal/handler/main_test.go
new file mode 100644
index 0000000000..0adc367c3f
--- /dev/null
+++ b/services/logs2notifications/internal/handler/main_test.go
@@ -0,0 +1,18 @@
+package handler
+
+import (
+ "bytes"
+ "reflect"
+ "testing"
+)
+
+func checkEqual(t *testing.T, got, want interface{}, msgs ...interface{}) {
+ if !reflect.DeepEqual(got, want) {
+ buf := bytes.Buffer{}
+ buf.WriteString("got:\n[%v]\nwant:\n[%v]\n")
+ for _, v := range msgs {
+ buf.WriteString(v.(string))
+ }
+ t.Errorf(buf.String(), got, want)
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/rocketchat_events.go b/services/logs2notifications/internal/handler/rocketchat_events.go
new file mode 100644
index 0000000000..b8f0b8ce8f
--- /dev/null
+++ b/services/logs2notifications/internal/handler/rocketchat_events.go
@@ -0,0 +1,227 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+)
+
+// RocketChatData .
+type RocketChatData struct {
+ Channel string `json:"channel"`
+ Attachments []RocketChatAttachment `json:"attachments"`
+}
+
+// RocketChatAttachment .
+type RocketChatAttachment struct {
+ Text string `json:"text"`
+ Color string `json:"color"`
+ Fields []RocketChatAttachmentField `json:"fields"`
+}
+
+// RocketChatAttachmentField .
+type RocketChatAttachmentField struct {
+ Short bool `json:"short"`
+ Title string `json:"title"`
+ Value string `json:"value"`
+}
+
+// SendToRocketChat .
+func SendToRocketChat(notification *Notification, channel, webhook, appID string) {
+
+ emoji, color, template, err := getRocketChatEvent(notification.Event)
+
+ var text string
+ switch template {
+ case "mergeRequestOpened":
+ text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) opened in [%s](%s)",
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "mergeRequestUpdated":
+ text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) updated in [%s](%s)",
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "mergeRequestClosed":
+ text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) closed in [%s](%s)",
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "deleteEnvironment":
+ text = fmt.Sprintf("*[%s]* delete trigger `%s`",
+ notification.Meta.ProjectName,
+ notification.Meta.EnvironmentName,
+ )
+ case "repoPushHandled":
+ text = fmt.Sprintf("*[%s]* [%s](%s/tree/%s)",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s ([%s](%s))",
+ text,
+ notification.Meta.ShortSha,
+ notification.Meta.CommitURL,
+ )
+ }
+ text = fmt.Sprintf("%s pushed in [%s](%s)",
+ text,
+ notification.Meta.RepoFullName,
+ notification.Meta.RepoURL,
+ )
+ case "repoPushSkipped":
+ text = fmt.Sprintf("*[%s]* [%s](%s/tree/%s)",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s ([%s](%s))",
+ text,
+ notification.Meta.ShortSha,
+ notification.Meta.CommitURL,
+ )
+ }
+ text = fmt.Sprintf("%s pushed in [%s](%s) *deployment skipped*",
+ text,
+ notification.Meta.RepoFullName,
+ notification.Meta.RepoURL,
+ )
+ case "deployEnvironment":
+ text = fmt.Sprintf("*[%s]* API deploy trigger `%s`",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s (%s)",
+ text,
+ notification.Meta.ShortSha,
+ )
+ }
+ default:
+ text = fmt.Sprintf("*[%s]* Event received for `%s`",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ }
+
+ data := RocketChatData{
+ Channel: channel,
+ Attachments: []RocketChatAttachment{{
+ Text: fmt.Sprintf("%s %s", emoji, text),
+ Color: color,
+ Fields: []RocketChatAttachmentField{{
+ Short: true,
+ Title: "Source",
+ Value: appID,
+ }},
+ }},
+ }
+
+ jsonBytes, _ := json.Marshal(data)
+ req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(jsonBytes))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Content-Length", fmt.Sprintf("%d", len(jsonBytes)))
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ panic(err)
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to rocketchat", notification.Event))
+}
+
+func getRocketChatEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := rocketChatEventTypeMap[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+type rocketChatEvent struct {
+ Emoji string `json:"emoji"`
+ Color string `json:"color"`
+ Template string `json:"template"`
+}
+
+var rocketChatEventTypeMap = map[string]rocketChatEvent{
+ "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+
+ "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+
+ "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+
+ "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+
+ "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+
+ // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+}
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
new file mode 100644
index 0000000000..12b02c3c71
--- /dev/null
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -0,0 +1,64 @@
+package handler
+
+import (
+ "fmt"
+)
+
+func getSlackEvent(msgEvent string) (string, string, error) {
+ if val, ok := slackEventTypeMap[msgEvent]; ok {
+ return val.Emoji, val.Color, nil
+ }
+ return "", "", fmt.Errorf("no matching event source")
+}
+
+type slackEvent struct {
+ Emoji string `json:"emoji"`
+ Color string `json:"color"`
+}
+
+var slackEventTypeMap = map[string]slackEvent{
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "good"},
+ "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "warning"},
+ "task:remove-openshift:retry": {Emoji: ":warning:", Color: "warning"},
+ "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "warning"},
+ "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "warning"},
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "danger"},
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "danger"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "danger"},
+ "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "danger"},
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "danger"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "danger"},
+}
diff --git a/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql b/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
new file mode 100644
index 0000000000..a1f4eca381
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
@@ -0,0 +1,41 @@
+query (
+ $name: String!
+) {
+ projectByName(
+ name: $name
+ ) {
+ id
+ name
+ notifications {
+ ... on NotificationSlack {
+ __typename
+ webhook
+ name
+ channel
+ }
+ ... on NotificationRocketChat {
+ __typename
+ webhook
+ name
+ channel
+ }
+ ... on NotificationEmail {
+ __typename
+ emailAddress
+ name
+ }
+ ... on NotificationMicrosoftTeams {
+ __typename
+ webhook
+ name
+ }
+ ... on NotificationWebhook {
+ __typename
+ webhook
+ name
+ contentType
+ notificationSeverityThreshold
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/client.go b/services/logs2notifications/internal/lagoon/client/client.go
new file mode 100644
index 0000000000..9ca3bb2624
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/client.go
@@ -0,0 +1,93 @@
+//go:generate go-bindata -pkg lgraphql -o lgraphql/lgraphql.go -nometadata _lgraphql/
+
+// Package client implements the interfaces required by the parent lagoon
+// package.
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/machinebox/graphql"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client/lgraphql"
+)
+
+// Client implements the lagoon package interfaces for the Lagoon GraphQL API.
+type Client struct {
+ userAgent string
+ token string
+ client *graphql.Client
+}
+
+// New creates a new Client for the given endpoint.
+func New(endpoint, token, userAgent string, debug bool) *Client {
+ if debug {
+ return &Client{
+ userAgent: userAgent,
+ token: token,
+ client: graphql.NewClient(endpoint,
+ // enable debug logging to stderr
+ func(c *graphql.Client) {
+ l := log.New(os.Stderr, "graphql", 0)
+ c.Log = func(s string) {
+ l.Println(s)
+ }
+ }),
+ }
+ }
+ return &Client{
+ userAgent: userAgent,
+ token: token,
+ client: graphql.NewClient(endpoint),
+ }
+}
+
+// newRequest constructs a graphql request.
+// assetName is the name of the graphql query template in _graphql/.
+// varStruct is converted to a map of variables for the template.
+func (c *Client) newRequest(
+ assetName string, varStruct interface{}) (*graphql.Request, error) {
+
+ q, err := lgraphql.Asset(assetName)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't get asset: %w", err)
+ }
+
+ return c.doRequest(string(q), varStruct)
+}
+
+func (c *Client) doRequest(query string, varStruct interface{}) (*graphql.Request, error) {
+ vars, err := structToVarMap(varStruct)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't convert struct to map: %w", err)
+ }
+
+ req := graphql.NewRequest(query)
+ for key, value := range vars {
+ req.Var(key, value)
+ }
+
+ headers := map[string]string{
+ "User-Agent": c.userAgent,
+ "Authorization": fmt.Sprintf("Bearer %s", c.token),
+ }
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ return req, nil
+}
+
+// structToVarMap encodes the given struct to a map. The idea is that by
+// round-tripping through Marshal/Unmarshal, omitempty is applied to the
+// zero-valued fields.
+func structToVarMap(
+ varStruct interface{}) (vars map[string]interface{}, err error) {
+ data, err := json.Marshal(varStruct)
+ if err != nil {
+ return vars, err
+ }
+ return vars, json.Unmarshal(data, &vars)
+}
diff --git a/services/logs2notifications/internal/lagoon/client/client_test.go b/services/logs2notifications/internal/lagoon/client/client_test.go
new file mode 100644
index 0000000000..c0e17ec55b
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/client_test.go
@@ -0,0 +1,81 @@
+package client_test
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
+)
+
+type testStruct0 struct {
+ Foo string `json:"foo"`
+ Bar uint `json:"bar"`
+ Baz string `json:"baz,omitempty"`
+ Quux uint `json:"quux,omitempty"`
+}
+
+func TestStructToVarMap(t *testing.T) {
+ var testCases = map[string]struct {
+ input testStruct0
+ expect map[string]interface{}
+ }{
+ "simple struct": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 8,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(8),
+ },
+ },
+ "keep zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ },
+ },
+ "omit zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ Baz: "",
+ Quux: 0,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ },
+ },
+ "keep non-zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ Baz: "hi",
+ Quux: 9,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ "baz": "hi",
+ "quux": float64(9),
+ },
+ },
+ }
+ for name, tc := range testCases {
+ t.Run(name, func(tt *testing.T) {
+ vars, err := client.StructToVarMap(&tc.input)
+ if err != nil {
+ tt.Error(err)
+ }
+ if !reflect.DeepEqual(vars, tc.expect) {
+ tt.Logf("result:\n%s\nexpected:\n%s", vars, tc.expect)
+ tt.Errorf("result does not match expected")
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/helper_test.go b/services/logs2notifications/internal/lagoon/client/helper_test.go
new file mode 100644
index 0000000000..6561a9155d
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/helper_test.go
@@ -0,0 +1,6 @@
+package client
+
+// StructToVarMap exposes the private client.structToVarMap for tests.
+func StructToVarMap(varStruct interface{}) (map[string]interface{}, error) {
+ return structToVarMap(varStruct)
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go b/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
new file mode 100644
index 0000000000..3e7a131df5
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
@@ -0,0 +1,247 @@
+// Code generated by go-bindata. (@generated) DO NOT EDIT.
+
+ //Package lgraphql generated by go-bindata.// sources:
+// _lgraphql/projectNotifications.graphql
+package lgraphql
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+// Name return file name
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+
+// Size return file size
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+
+// Mode return file mode
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+
+// ModTime return file modify time
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+
+// IsDir return file whether a directory
+func (fi bindataFileInfo) IsDir() bool {
+ return fi.mode&os.ModeDir != 0
+}
+
+// Sys return file is sys mode
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var __lgraphqlProjectnotificationsGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x92\xc1\x4a\xc4\x30\x10\x86\xcf\xf6\x29\x46\xf0\xb0\x5e\xfa\x00\xde\x54\x3c\xba\x07\x5b\xf0\xb8\xc4\x74\xd6\x8c\x6d\x67\x6a\x32\x2a\x41\xfa\xee\x92\x96\xc5\xb6\x2a\x45\x50\xd8\xff\x94\xfc\xf9\x66\xf8\x93\xc9\xf3\x0b\xfa\x08\x9b\x0c\xe0\x8c\x4d\x8b\x17\x50\xa8\x27\x7e\x3c\xcd\xce\xe1\x3d\x03\x00\xe8\xbc\x3c\xa1\xd5\xab\xb8\x35\x2d\x6e\x06\x2b\x69\x84\x87\x9a\xc1\x3b\xe0\x49\x54\xcd\xb0\xcf\x8d\x28\xed\xc9\x1a\x25\xe1\x30\xe1\x93\xf2\x3c\x07\x61\xd8\x4e\x90\xa2\x31\xb6\x5e\x60\x49\xbb\x9d\xc6\x0e\x67\x9d\x0f\x7a\xc3\x07\x27\x52\x7f\xf1\xbf\x85\xad\x33\xcc\xd8\xcc\xfc\x7e\x2d\xd4\x9d\xd8\x1a\xf5\xda\x19\x3d\xb6\x64\x37\xad\xa1\xe6\x77\xa1\x30\x95\x5c\x56\x95\xc7\x10\xd6\x93\xad\x26\xb8\x25\xeb\x25\xc8\x5e\x4b\x34\xed\x72\xc0\x7f\xf5\x3e\xab\x29\xee\xc7\x4e\xff\x39\x1e\x61\x45\xd6\x32\x76\xcb\xb3\x93\xe9\x17\x2f\xf0\x15\x3d\x69\x2c\x9d\xc7\xe0\xa4\xa9\x7e\xb8\xc7\xb8\xea\xb3\xfe\x23\x00\x00\xff\xff\x7f\x5a\x0f\xed\x8c\x03\x00\x00")
+
+func _lgraphqlProjectnotificationsGraphqlBytes() ([]byte, error) {
+ return bindataRead(
+ __lgraphqlProjectnotificationsGraphql,
+ "_lgraphql/projectNotifications.graphql",
+ )
+}
+
+func _lgraphqlProjectnotificationsGraphql() (*asset, error) {
+ bytes, err := _lgraphqlProjectnotificationsGraphqlBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "_lgraphql/projectNotifications.graphql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if err != nil {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "_lgraphql/projectNotifications.graphql": _lgraphqlProjectnotificationsGraphql,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+
+var _bintree = &bintree{nil, map[string]*bintree{
+ "_lgraphql": &bintree{nil, map[string]*bintree{
+ "projectNotifications.graphql": &bintree{_lgraphqlProjectnotificationsGraphql, map[string]*bintree{}},
+ }},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
diff --git a/services/logs2notifications/internal/lagoon/client/query.go b/services/logs2notifications/internal/lagoon/client/query.go
new file mode 100644
index 0000000000..a1e9a2cdca
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/query.go
@@ -0,0 +1,25 @@
+package client
+
+import (
+ "context"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// NotificationsForProjectByName gets all notifications for a project
+func (c *Client) NotificationsForProjectByName(
+ ctx context.Context, name string, project *schema.Project) error {
+ req, err := c.newRequest("_lgraphql/projectNotifications.graphql",
+ map[string]interface{}{
+ "name": name,
+ })
+ if err != nil {
+ return err
+ }
+
+ return c.client.Run(ctx, req, &struct {
+ Response *schema.Project `json:"projectByName"`
+ }{
+ Response: project,
+ })
+}
diff --git a/services/logs2notifications/internal/lagoon/jwt/jwt.go b/services/logs2notifications/internal/lagoon/jwt/jwt.go
new file mode 100644
index 0000000000..4284685b76
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/jwt/jwt.go
@@ -0,0 +1,30 @@
+package jwt
+
+import (
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+// LagoonClaims is a set of JWT claims used by Lagoon.
+type LagoonClaims struct {
+ Role string `json:"role"`
+ jwt.StandardClaims
+}
+
+// OneMinuteAdminToken returns a JWT admin token valid for one minute.
+func OneMinuteAdminToken(secret, audience, subject, issuer string) (string, error) {
+ now := time.Now()
+ claims := LagoonClaims{
+ Role: "admin",
+ StandardClaims: jwt.StandardClaims{
+ Audience: audience,
+ ExpiresAt: now.Unix() + 60,
+ IssuedAt: now.Unix(),
+ Subject: subject,
+ Issuer: issuer,
+ },
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString([]byte(secret))
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/project.go b/services/logs2notifications/internal/lagoon/project.go
new file mode 100644
index 0000000000..7208fc3501
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/project.go
@@ -0,0 +1,20 @@
+// Package lagoon implements high-level functions for interacting with the
+// Lagoon API.
+package lagoon
+
+import (
+ "context"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// Project interface contains methods for projects in lagoon.
+type Project interface {
+ NotificationsForProjectByName(ctx context.Context, name string, result *schema.Project) error
+}
+
+// NotificationsForProject gets notifications for a project.
+func NotificationsForProject(ctx context.Context, name string, m Project) (*schema.Project, error) {
+ result := schema.Project{}
+ return &result, m.NotificationsForProjectByName(ctx, name, &result)
+}
diff --git a/services/logs2notifications/internal/schema/notifications.go b/services/logs2notifications/internal/schema/notifications.go
new file mode 100644
index 0000000000..33aeff6f61
--- /dev/null
+++ b/services/logs2notifications/internal/schema/notifications.go
@@ -0,0 +1,107 @@
+package schema
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Notifications represents possible Lagoon notification types.
+// These are unmarshalled from a projectByName query response.
+type Notifications struct {
+ Slack []NotificationSlack
+ RocketChat []NotificationRocketChat
+ Email []NotificationEmail
+ MicrosoftTeams []NotificationMicrosoftTeams
+ Webhook []NotificationWebhook
+}
+
+// NotificationSlack is based on the Lagoon API type.
+type NotificationSlack struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ Channel string `json:"channel"`
+}
+
+// NotificationRocketChat is based on the Lagoon API type.
+type NotificationRocketChat struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ Channel string `json:"channel"`
+}
+
+// NotificationEmail is based on the Lagoon API type.
+type NotificationEmail struct {
+ Name string `json:"name"`
+ EmailAddress string `json:"emailAddress"`
+}
+
+// NotificationMicrosoftTeams is based on the Lagoon API type.
+type NotificationMicrosoftTeams struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+}
+
+// NotificationWebhook is based on the Lagoon API type.
+type NotificationWebhook struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ ContentType string `json:"contentType"`
+ NotificationSeverityThreshold string `json:"notificationSeverityThreshold"`
+}
+
+// UnmarshalJSON unmashals a quoted json string to the Notification values
+// returned from the Lagoon API.
+func (n *Notifications) UnmarshalJSON(b []byte) error {
+ var nArray []map[string]string
+ err := json.Unmarshal(b, &nArray)
+ if err != nil {
+ return err
+ }
+ for _, nMap := range nArray {
+ if len(nMap) == 0 {
+ // Unsupported notification type returns an empty map.
+ // This happens when the lagoon API being targeted is actually a higher
+ // version than configured.
+ continue
+ }
+ switch nMap["__typename"] {
+ case "NotificationSlack":
+ n.Slack = append(n.Slack,
+ NotificationSlack{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ Channel: nMap["channel"],
+ })
+ case "NotificationRocketChat":
+ n.RocketChat = append(n.RocketChat,
+ NotificationRocketChat{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ Channel: nMap["channel"],
+ })
+ case "NotificationEmail":
+ n.Email = append(n.Email,
+ NotificationEmail{
+ Name: nMap["name"],
+ EmailAddress: nMap["emailAddress"],
+ })
+ case "NotificationMicrosoftTeams":
+ n.MicrosoftTeams = append(n.MicrosoftTeams,
+ NotificationMicrosoftTeams{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ })
+ case "NotificationWebhook":
+ n.Webhook = append(n.Webhook,
+ NotificationWebhook{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ ContentType: nMap["contentType"],
+ NotificationSeverityThreshold: nMap["notificationSeverityThreshold"],
+ })
+ default:
+ return fmt.Errorf("unknown notification type: %v", nMap["__typename"])
+ }
+ }
+ return nil
+}
diff --git a/services/logs2notifications/internal/schema/project.go b/services/logs2notifications/internal/schema/project.go
new file mode 100644
index 0000000000..d806ac94a8
--- /dev/null
+++ b/services/logs2notifications/internal/schema/project.go
@@ -0,0 +1,8 @@
+package schema
+
+// Project is based on the Lagoon API type.
+type Project struct {
+ ID uint `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Notifications *Notifications `json:"notifications,omitempty"`
+}
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
new file mode 100644
index 0000000000..1d2a6ded54
--- /dev/null
+++ b/services/logs2notifications/main.go
@@ -0,0 +1,185 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/cheshir/go-mq"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/handler"
+)
+
+var (
+ httpListenPort = os.Getenv("HTTP_LISTEN_PORT")
+ mqUser string
+ mqPass string
+ mqHost string
+ mqPort string
+ mqWorkers int
+ rabbitReconnectRetryInterval int
+ startupConnectionAttempts int
+ startupConnectionInterval int
+ lagoonAPIHost string
+ lagoonAppID string
+ jwtTokenSigningKey string
+ jwtAudience string
+ actionsQueueName string
+ actionsExchange string
+ jwtSubject string
+ jwtIssuer string
+)
+
+func main() {
+ flag.StringVar(&lagoonAppID, "lagoon-app-id", "logs2notifications",
+ "The appID to use that will be sent with messages.")
+ flag.StringVar(&mqUser, "rabbitmq-username", "guest",
+ "The username of the rabbitmq user.")
+ flag.StringVar(&mqPass, "rabbitmq-password", "guest",
+ "The password for the rabbitmq user.")
+ flag.StringVar(&mqHost, "rabbitmq-hostname", "localhost",
+ "The hostname for the rabbitmq host.")
+ flag.StringVar(&mqPort, "rabbitmq-port", "5672",
+ "The port for the rabbitmq host.")
+ flag.IntVar(&mqWorkers, "rabbitmq-queue-workers", 1,
+ "The number of workers to start with.")
+ flag.IntVar(&rabbitReconnectRetryInterval, "rabbitmq-reconnect-retry-interval", 30,
+ "The retry interval for rabbitmq.")
+ flag.IntVar(&startupConnectionAttempts, "startup-connection-attempts", 10,
+ "The number of startup attempts before exiting.")
+ flag.IntVar(&startupConnectionInterval, "startup-connection-interval-seconds", 30,
+ "The duration between startup attempts.")
+ flag.StringVar(&lagoonAPIHost, "lagoon-api-host", "http://localhost:3000/graphql",
+ "The host for the lagoon api.")
+ flag.StringVar(&jwtTokenSigningKey, "jwt-token-signing-key", "super-secret-string",
+ "The jwt signing token key or secret.")
+ flag.StringVar(&jwtAudience, "jwt-audience", "api.dev",
+ "The jwt audience.")
+ flag.StringVar(&jwtSubject, "jwt-subject", "logs2notifications",
+ "The jwt audience.")
+ flag.StringVar(&jwtIssuer, "jwt-issuer", "logs2notifications",
+ "The jwt audience.")
+ flag.StringVar(&actionsQueueName, "actions-queue-name", "lagoon-actions:items",
+ "The name of the queue in rabbitmq to use.")
+ flag.StringVar(&actionsExchange, "actions-exchange", "lagoon-actions",
+ "The name of the exchange in rabbitmq to use.")
+ flag.Parse()
+
+ // get overrides from environment variables
+ mqUser = getEnv("RABBITMQ_USERNAME", mqUser)
+ mqPass = getEnv("RABBITMQ_PASSWORD", mqPass)
+ mqHost = getEnv("RABBITMQ_ADDRESS", mqHost)
+ mqPort = getEnv("RABBITMQ_PORT", mqPort)
+ lagoonAPIHost = getEnv("GRAPHQL_ENDPOINT", lagoonAPIHost)
+ jwtTokenSigningKey = getEnv("JWT_SECRET", jwtTokenSigningKey)
+ jwtAudience = getEnv("JWT_AUDIENCE", jwtAudience)
+ jwtSubject = getEnv("JWT_SUBJECT", jwtSubject)
+ jwtIssuer = getEnv("JWT_ISSUER", jwtIssuer)
+ actionsQueueName = getEnv("ACTIONS_QUEUE_NAME", actionsQueueName)
+ actionsExchange = getEnv("ACTIONS_EXCHANGE", actionsExchange)
+
+ enableDebug := true
+
+ // configure the backup handler settings
+ broker := handler.RabbitBroker{
+ Hostname: fmt.Sprintf("%s:%s", mqHost, mqPort),
+ Username: mqUser,
+ Password: mqPass,
+ QueueName: actionsQueueName,
+ ExchangeName: actionsExchange,
+ }
+ graphQLConfig := handler.LagoonAPI{
+ Endpoint: lagoonAPIHost,
+ TokenSigningKey: jwtTokenSigningKey,
+ JWTAudience: jwtAudience,
+ JWTSubject: jwtSubject,
+ JWTIssuer: jwtIssuer,
+ }
+
+ log.Println("logs2notifications running")
+
+ config := mq.Config{
+ ReconnectDelay: time.Duration(rabbitReconnectRetryInterval) * time.Second,
+ Exchanges: mq.Exchanges{
+ {
+ Name: "lagoon-logs",
+ Type: "direct",
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Consumers: mq.Consumers{
+ {
+ Name: "notifications-queue",
+ Queue: "lagoon-logs:notifications",
+ Workers: mqWorkers,
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Queues: mq.Queues{
+ {
+ Name: "lagoon-logs:notifications",
+ Exchange: "lagoon-logs",
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Producers: mq.Producers{
+ {
+ Name: "lagoon-logs",
+ Exchange: "lagoon-logs",
+ Options: mq.Options{
+ "app_id": lagoonAppID,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ DSN: fmt.Sprintf("amqp://%s:%s@%s/", broker.Username, broker.Password, broker.Hostname),
+ }
+
+ messaging := handler.NewMessaging(config,
+ graphQLConfig,
+ startupConnectionAttempts,
+ startupConnectionInterval,
+ enableDebug,
+ lagoonAppID,
+ )
+
+ // start the consumer
+ messaging.Consumer()
+
+}
+
+func getEnv(key, fallback string) string {
+ if value, ok := os.LookupEnv(key); ok {
+ return value
+ }
+ return fallback
+}
+
+// accepts fallback values 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
+// anything else is false.
+func getEnvBool(key string, fallback bool) bool {
+ if value, ok := os.LookupEnv(key); ok {
+ rVal, _ := strconv.ParseBool(value)
+ return rVal
+ }
+ return fallback
+}
From f109e270354ae2a692c995619ef54a808ea4fcbf Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Thu, 28 Oct 2021 09:22:04 +1100
Subject: [PATCH 02/17] feat: add more logs2x services
---
services/logs2notifications/Dockerfile | 2 -
services/logs2notifications/go.mod | 4 +-
services/logs2notifications/go.sum | 16 +
.../internal/handler/email_events.go | 421 ++++++++++++++++++
.../internal/handler/main.go | 128 +++---
.../internal/handler/microsoftteams_events.go | 279 ++++++++++++
.../internal/handler/rocketchat_events.go | 165 +++++--
.../internal/handler/s3_events.go | 67 +++
.../internal/handler/slack_events.go | 151 ++++---
services/logs2notifications/main.go | 61 ++-
10 files changed, 1130 insertions(+), 164 deletions(-)
create mode 100644 services/logs2notifications/internal/handler/email_events.go
create mode 100644 services/logs2notifications/internal/handler/microsoftteams_events.go
create mode 100644 services/logs2notifications/internal/handler/s3_events.go
diff --git a/services/logs2notifications/Dockerfile b/services/logs2notifications/Dockerfile
index 34ef5e9920..6c2bdaff6e 100644
--- a/services/logs2notifications/Dockerfile
+++ b/services/logs2notifications/Dockerfile
@@ -33,6 +33,4 @@ ENV JWT_SECRET=super-secret-string \
RABBITMQ_PORT=5672 \
RABBITMQ_USERNAME=guest \
RABBITMQ_PASSWORD=guest
-
-ENTRYPOINT ["/sbin/tini", "--", "/lagoon/entrypoints.sh"]
CMD ["/app/logs2notifications"]
\ No newline at end of file
diff --git a/services/logs2notifications/go.mod b/services/logs2notifications/go.mod
index 06e5305c9a..aa3e6ae785 100644
--- a/services/logs2notifications/go.mod
+++ b/services/logs2notifications/go.mod
@@ -3,6 +3,7 @@ module github.com/uselagoon/lagoon/services/logs2notifications
go 1.14
require (
+ github.com/aws/aws-sdk-go v1.41.11
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
github.com/cheshir/go-mq v1.0.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
@@ -10,6 +11,7 @@ require (
github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225
github.com/matryer/is v1.4.0 // indirect
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2
+ github.com/slack-go/slack v0.9.5
github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
@@ -17,4 +19,4 @@ require (
// Fixes for AppID
replace github.com/cheshir/go-mq v1.0.2 => github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead
-replace github.com/NeowayLabs/wabbit v0.0.0-20200409220312-12e68ab5b0c6 => github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204
\ No newline at end of file
+replace github.com/NeowayLabs/wabbit v0.0.0-20200409220312-12e68ab5b0c6 => github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204
diff --git a/services/logs2notifications/go.sum b/services/logs2notifications/go.sum
index bcac1aac42..4892db91f2 100644
--- a/services/logs2notifications/go.sum
+++ b/services/logs2notifications/go.sum
@@ -5,6 +5,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
+github.com/aws/aws-sdk-go v1.41.11 h1:QLouWsiYQ8i22kD8k58Dpdhio1A0MpT7bg9ZNXqEjuI=
+github.com/aws/aws-sdk-go v1.41.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -31,6 +33,7 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsouza/go-dockerclient v1.7.3/go.mod h1:8xfZB8o9SptLNJ13VoV5pMiRbZGWkU/Omu5VOu/KC9Y=
+github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -47,8 +50,13 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -75,6 +83,7 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -89,6 +98,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/slack-go/slack v0.9.5 h1:j7uOUDowybWf9eSgZg/AbGx6J1OPJB6SE8Z5dNl6Mtw=
+github.com/slack-go/slack v0.9.5/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -124,6 +135,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -145,13 +157,17 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/services/logs2notifications/internal/handler/email_events.go b/services/logs2notifications/internal/handler/email_events.go
new file mode 100644
index 0000000000..e0e805942b
--- /dev/null
+++ b/services/logs2notifications/internal/handler/email_events.go
@@ -0,0 +1,421 @@
+package handler
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "net/smtp"
+ "text/template"
+)
+
+var htmlTemplate = `
+
+
+ {{.Title}}
+
+
+
+
+
+
+
+
{{.Emoji}} [{{.ProjectName}}]
+
+ {{.MainHTML}}
+
+
+
+
+`
+
+// SendToEmail .
+func SendToEmail(notification *Notification, emailAddress, appID string) {
+
+ emoji, color, tpl, err := getEmailEvent(notification.Event)
+ if err != nil {
+ return
+ }
+ var mainHTML, plainText, subject, additional string
+ switch tpl {
+ case "mergeRequestOpened":
+ mainHTML += fmt.Sprintf(`PR #%s (%s opened in %s`,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoURL,
+ notification.Meta.RepoName,
+ )
+ plainText += fmt.Sprintf(`[%s] PR #%s - %s opened in %s`,
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.RepoName,
+ )
+ subject += plainText
+ case "mergeRequestUpdated":
+ mainHTML += fmt.Sprintf(`PR #%s (%s updated in %s`,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoURL,
+ notification.Meta.RepoName,
+ )
+ plainText += fmt.Sprintf(`[%s] PR #%s - %s updated in %s`,
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.RepoName,
+ )
+ subject += plainText
+ case "mergeRequestClosed":
+ mainHTML += fmt.Sprintf(`PR #%s (%s closed in %s`,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoURL,
+ notification.Meta.RepoName,
+ )
+ plainText += fmt.Sprintf(`[%s] PR #%s - %s closed in %s`,
+ notification.Meta.ProjectName,
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.RepoName,
+ )
+ subject += plainText
+ case "deleteEnvironment":
+ mainHTML += fmt.Sprintf("Deleted environment %s
",
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf("[%s] deleted environment %s",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ subject += plainText
+ case "repoPushHandled":
+ mainHTML += fmt.Sprintf(`%s`,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf(`[%s] %s`,
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ mainHTML += fmt.Sprintf(`%s %s`,
+ mainHTML,
+ notification.Meta.CommitURL,
+ notification.Meta.ShortSha,
+ )
+ plainText += fmt.Sprintf(`%s (%s)`,
+ plainText,
+ notification.Meta.ShortSha,
+ )
+ }
+ mainHTML += fmt.Sprintf(`%s pushed in %s`,
+ mainHTML,
+ notification.Meta.RepoURL,
+ notification.Meta.RepoFullName,
+ )
+ plainText += fmt.Sprintf(`%s pushed in %s`,
+ plainText,
+ notification.Meta.RepoFullName,
+ )
+ subject += plainText
+ case "repoPushSkipped":
+ mainHTML += fmt.Sprintf(`%s`,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf(`[%s] %s`,
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ mainHTML += fmt.Sprintf(`%s %s`,
+ mainHTML,
+ notification.Meta.CommitURL,
+ notification.Meta.ShortSha,
+ )
+ plainText += fmt.Sprintf(`%s (%s)`,
+ plainText,
+ notification.Meta.ShortSha,
+ )
+ }
+ mainHTML += fmt.Sprintf(`%s pushed in %s deployment skipped`,
+ mainHTML,
+ notification.Meta.RepoURL,
+ notification.Meta.RepoFullName,
+ )
+ plainText += fmt.Sprintf(`%s pushed in %s *deployment skipped*`,
+ plainText,
+ notification.Meta.RepoFullName,
+ )
+ subject += plainText
+ case "deployEnvironment":
+ mainHTML += fmt.Sprintf("Deployment triggered %s
",
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf("[%s] Deployment triggered on branch %s",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ mainHTML += fmt.Sprintf(`%s (%s)`,
+ mainHTML,
+ notification.Meta.ShortSha,
+ )
+ plainText += fmt.Sprintf(`%s (%s)`,
+ plainText,
+ notification.Meta.ShortSha,
+ )
+ }
+ subject += plainText
+ case "removeFinished":
+ mainHTML += fmt.Sprintf("Remove %s
", notification.Meta.OpenshiftProject)
+ plainText += fmt.Sprintf("[%s] remove %s", notification.Meta.ProjectName, notification.Meta.OpenshiftProject)
+ subject += plainText
+ case "notDeleted":
+ mainHTML += fmt.Sprintf("%s
not deleted.",
+ notification.Meta.OpenshiftProject,
+ )
+ plainText += fmt.Sprintf("[%s] %s not deleted.",
+ notification.Meta.ProjectName,
+ notification.Meta.OpenshiftProject,
+ )
+ subject += plainText
+ plainText += fmt.Sprintf("%s", notification.Meta.Error)
+ case "deployError":
+ mainHTML += fmt.Sprintf("[%s]",
+ notification.Meta.ProjectName,
+ )
+ plainText += fmt.Sprintf("[%s]",
+ notification.Meta.ProjectName,
+ )
+ if notification.Meta.ShortSha != "" {
+ mainHTML += fmt.Sprintf(` %s
(%s)`,
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ plainText += fmt.Sprintf(` %s (%s)`,
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ mainHTML += fmt.Sprintf(` %s
`,
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf(` %s`,
+ notification.Meta.BranchName,
+ )
+ }
+ mainHTML += fmt.Sprintf(` Build %s
error.`,
+ notification.Meta.BuildName,
+ )
+ plainText += fmt.Sprintf(` Build %s error.`,
+ notification.Meta.BuildName,
+ )
+ subject += plainText
+ if notification.Meta.LogLink != "" {
+ mainHTML += fmt.Sprintf(` Logs`,
+ notification.Meta.LogLink,
+ )
+ plainText += fmt.Sprintf(` [Logs](%s)`,
+ notification.Meta.LogLink,
+ )
+ }
+ case "deployFinished":
+ mainHTML += fmt.Sprintf("[%s]",
+ notification.Meta.ProjectName,
+ )
+ plainText += fmt.Sprintf("[%s]",
+ notification.Meta.ProjectName,
+ )
+ if notification.Meta.ShortSha != "" {
+ mainHTML += fmt.Sprintf(` %s
(%s)`,
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ plainText += fmt.Sprintf(` %s (%s)`,
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ mainHTML += fmt.Sprintf(` %s
`,
+ notification.Meta.BranchName,
+ )
+ plainText += fmt.Sprintf(` %s`,
+ notification.Meta.BranchName,
+ )
+ }
+ mainHTML += fmt.Sprintf(` Build %s
complete.`,
+ notification.Meta.BuildName,
+ )
+ plainText += fmt.Sprintf(` Build %s complete.`,
+ notification.Meta.BuildName,
+ )
+ subject += plainText
+ if notification.Meta.LogLink != "" {
+ mainHTML += fmt.Sprintf(` Logs`,
+ notification.Meta.LogLink,
+ )
+ plainText += fmt.Sprintf(` [Logs](%s)`,
+ notification.Meta.LogLink,
+ )
+ }
+ additional += fmt.Sprintf(` - %s
`,
+ notification.Meta.Route,
+ notification.Meta.Route,
+ )
+ plainText += fmt.Sprintf("%s %s\n",
+ plainText,
+ notification.Meta.Route,
+ )
+ if len(notification.Meta.Routes) != 0 {
+ for _, r := range notification.Meta.Routes {
+ if r != notification.Meta.Route {
+ additional += fmt.Sprintf(` - %s
`,
+ r,
+ r,
+ )
+ plainText += fmt.Sprintf(" %s\n",
+ r,
+ )
+ }
+ }
+ }
+ mainHTML += fmt.Sprintf(`
`)
+ }
+
+ t, _ := template.New("email").Parse(htmlTemplate)
+ var body bytes.Buffer
+
+ mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
+ body.Write([]byte(fmt.Sprintf("From: Lagoon Notifications\nSubject: %s \n%s\n\n", subject, mimeHeaders)))
+
+ t.Execute(&body, struct {
+ Color string
+ Emoji string
+ Title string
+ ProjectName string
+ MainHTML string
+ Additional string
+ }{
+ Title: plainText,
+ Color: color,
+ Emoji: emoji,
+ ProjectName: notification.Meta.ProjectName,
+ MainHTML: mainHTML,
+ Additional: additional,
+ })
+ // Configuration
+ from := "notifications@lagoon.sh"
+ password := ""
+ to := []string{emailAddress}
+ smtpHost := "localhost"
+ smtpPort := "1025"
+
+ // Create authentication
+ auth := smtp.PlainAuth("", from, password, smtpHost)
+ // Send actual message
+ err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
+ if err != nil {
+ log.Printf("Error sending message to email: %v", err)
+ return
+ }
+ log.Println(fmt.Sprintf("Sent %s message to email", notification.Event))
+}
+
+func getEmailEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := emailEvent[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var emailEvent = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "‼️", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "‼️", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
index 9ccbd7cd1a..d430ff414d 100644
--- a/services/logs2notifications/internal/handler/main.go
+++ b/services/logs2notifications/internal/handler/main.go
@@ -12,17 +12,14 @@ import (
"github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon"
lclient "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
"github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/jwt"
- // "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
)
// RabbitBroker .
type RabbitBroker struct {
- Hostname string `json:"hostname"`
- Port string `json:"port"`
- Username string `json:"username,omitempty"`
- Password string `json:"password,omitempty"`
- QueueName string `json:"queueName"`
- ExchangeName string `json:"exchangeName"`
+ Hostname string `json:"hostname"`
+ Port string `json:"port"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
}
// LagoonAPI .
@@ -54,6 +51,12 @@ type Messaging struct {
ConnectionRetryInterval int
EnableDebug bool
LagoonAppID string
+ DisableSlack bool
+ DisableRocketChat bool
+ DisableMicrosoftTeams bool
+ DisableEmail bool
+ DisableWebhooks bool
+ DisableS3 bool
}
// Notification .
@@ -75,41 +78,48 @@ type Notification struct {
Host string `json:"host"`
IPAddress string `json:"ipAddress"`
} `json:"headers"`
- Project string `json:"project"`
- ProjectName string `json:"projectName"`
- BranchName string `json:"branchName`
- Event string `json:"event"`
- Level string `json:"level"`
- Message string `json:"message"`
- Timestamp string `json:"timestamp"`
- ShortSha string `json:"shortSha"`
- BuildName string `json:"buildName"`
- CommitURL string `json:"commitUrl"`
- Environment string `json:"environment"`
- EnvironmentID string `json:"environmentId"`
- EnvironmentName string `json:"environmentName"`
- Error string `json:"error"`
- JobName string `json:"jobName"`
- LogLink string `json:"logLink"`
- Name string `json:"name"`
- OpenshiftProject string `json:"openshiftProject"`
- PromoteSourceEnvironment string `json:"promoteSourceEnvironment"`
- PullrequestNumber string `json:"pullrequestNumber"`
- PullrequestTitle string `json:"pullrequestTitle"`
- PullrequestURL string `json:"pullrequestUrl"`
- RemoteID string `json:"remoteId"`
- RepoFullName string `json:"repoFullName"`
- RepoName string `json:"repoName"`
- RepoURL string `json:"repoUrl"`
- Route string `json:"route"`
- Routes string `json:"routes"`
- Task string `json:"task"`
+ Project string `json:"project"`
+ ProjectName string `json:"projectName"`
+ BranchName string `json:"branchName`
+ Event string `json:"event"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+ Timestamp string `json:"timestamp"`
+ ShortSha string `json:"shortSha"`
+ BuildName string `json:"buildName"`
+ CommitURL string `json:"commitUrl"`
+ Environment string `json:"environment"`
+ EnvironmentID string `json:"environmentId"`
+ EnvironmentName string `json:"environmentName"`
+ Error string `json:"error"`
+ JobName string `json:"jobName"`
+ LogLink string `json:"logLink"`
+ Name string `json:"name"`
+ OpenshiftProject string `json:"openshiftProject"`
+ PromoteSourceEnvironment string `json:"promoteSourceEnvironment"`
+ PullrequestNumber string `json:"pullrequestNumber"`
+ PullrequestTitle string `json:"pullrequestTitle"`
+ PullrequestURL string `json:"pullrequestUrl"`
+ RemoteID string `json:"remoteId"`
+ RepoFullName string `json:"repoFullName"`
+ RepoName string `json:"repoName"`
+ RepoURL string `json:"repoUrl"`
+ Route string `json:"route"`
+ Routes []string `json:"routes"`
+ Task string `json:"task"`
} `json:"meta"`
Message string `json:"message"`
}
+// EventMap .
+type EventMap struct {
+ Emoji string `json:"emoji"`
+ Color string `json:"color"`
+ Template string `json:"template"`
+}
+
// NewMessaging returns a messaging with config
-func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, startupInterval int, enableDebug bool, appID string) *Messaging {
+func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, startupInterval int, enableDebug bool, appID string, disableSlack, disableRocketChat, disableMicrosoftTeams, disableEmail, disableWebhooks, disableS3 bool) *Messaging {
return &Messaging{
Config: config,
LagoonAPI: lagoonAPI,
@@ -117,6 +127,12 @@ func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, st
ConnectionRetryInterval: startupInterval,
EnableDebug: enableDebug,
LagoonAppID: appID,
+ DisableSlack: disableSlack,
+ DisableRocketChat: disableRocketChat,
+ DisableMicrosoftTeams: disableMicrosoftTeams,
+ DisableEmail: disableEmail,
+ DisableWebhooks: disableWebhooks,
+ DisableS3: disableS3,
}
}
@@ -182,23 +198,31 @@ func (h *Messaging) Consumer() {
log.Println(err)
break
}
- // if len(projectNotifications.Notifications.Slack) > 0 {
- // fmt.Println(projectNotifications.Notifications.Slack)
- // }
- if len(projectNotifications.Notifications.RocketChat) > 0 {
- for _, rc := range projectNotifications.Notifications.RocketChat {
- SendToRocketChat(notification, rc.Channel, rc.Webhook, h.LagoonAppID)
+ if projectNotifications.Notifications != nil {
+ if len(projectNotifications.Notifications.Slack) > 0 && !h.DisableSlack {
+ for _, slack := range projectNotifications.Notifications.Slack {
+ SendToSlack(notification, slack.Channel, slack.Webhook, message.AppId())
+ }
+ }
+ if len(projectNotifications.Notifications.RocketChat) > 0 && !h.DisableRocketChat {
+ for _, rc := range projectNotifications.Notifications.RocketChat {
+ SendToRocketChat(notification, rc.Channel, rc.Webhook, message.AppId())
+ }
+ }
+ if len(projectNotifications.Notifications.Email) > 0 && !h.DisableEmail {
+ for _, email := range projectNotifications.Notifications.Email {
+ SendToEmail(notification, email.EmailAddress, message.AppId())
+ }
+ }
+ if len(projectNotifications.Notifications.MicrosoftTeams) > 0 && !h.DisableMicrosoftTeams {
+ for _, teams := range projectNotifications.Notifications.MicrosoftTeams {
+ SendToMicrosoftTeams(notification, teams.Webhook, message.AppId())
+ }
}
+ // if len(projectNotifications.Notifications.Webhook) > 0 {
+ // fmt.Println(projectNotifications.Notifications.Webhook)
+ // }
}
- // if len(projectNotifications.Notifications.Email) > 0 {
- // fmt.Println(projectNotifications.Notifications.Email)
- // }
- // if len(projectNotifications.Notifications.MicrosoftTeams) > 0 {
- // fmt.Println(projectNotifications.Notifications.MicrosoftTeams)
- // }
- // if len(projectNotifications.Notifications.Webhook) > 0 {
- // fmt.Println(projectNotifications.Notifications.Webhook)
- // }
}
message.Ack(false) // ack to remove from queue
})
diff --git a/services/logs2notifications/internal/handler/microsoftteams_events.go b/services/logs2notifications/internal/handler/microsoftteams_events.go
new file mode 100644
index 0000000000..cc99b05331
--- /dev/null
+++ b/services/logs2notifications/internal/handler/microsoftteams_events.go
@@ -0,0 +1,279 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+)
+
+// MicrosoftTeamsData .
+type MicrosoftTeamsData struct {
+ Type string `json:"@type"`
+ Context string `json:"@context"`
+ Summary string `json:"summary"`
+ Title string `json:"title"`
+ ThemeColor string `json:"themeColor"`
+ Sections []MicrosoftTeamsSection `json:"sections"`
+}
+
+// MicrosoftTeamsSection .
+type MicrosoftTeamsSection struct {
+ ActivityText string `json:"activityText"`
+ ActivityImage string `json:"activityImage"`
+}
+
+// SendToMicrosoftTeams .
+func SendToMicrosoftTeams(notification *Notification, webhook, appID string) {
+
+ emoji, color, template, err := getMicrosoftTeamsEvent(notification.Event)
+ if err != nil {
+ return
+ }
+
+ var text string
+ switch template {
+ case "mergeRequestOpened":
+ text = fmt.Sprintf("PR [#%s (%s)](%s) opened in [%s](%s)",
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "mergeRequestUpdated":
+ text = fmt.Sprintf("PR [#%s (%s)](%s) updated in [%s](%s)",
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "mergeRequestClosed":
+ text = fmt.Sprintf("PR [#%s (%s)](%s) closed in [%s](%s)",
+ notification.Meta.PullrequestNumber,
+ notification.Meta.PullrequestTitle,
+ notification.Meta.PullrequestURL,
+ notification.Meta.RepoName,
+ notification.Meta.RepoURL,
+ )
+ case "deleteEnvironment":
+ text = fmt.Sprintf("Deleting environment `%s`",
+ notification.Meta.EnvironmentName,
+ )
+ case "repoPushHandled":
+ text = fmt.Sprintf("[%s](%s/tree/%s)",
+ notification.Meta.BranchName,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s ([%s](%s))",
+ text,
+ notification.Meta.ShortSha,
+ notification.Meta.CommitURL,
+ )
+ }
+ text = fmt.Sprintf("%s pushed in [%s](%s)",
+ text,
+ notification.Meta.RepoFullName,
+ notification.Meta.RepoURL,
+ )
+ case "repoPushSkipped":
+ text = fmt.Sprintf("[%s](%s/tree/%s)",
+ notification.Meta.BranchName,
+ notification.Meta.RepoURL,
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s ([%s](%s))",
+ text,
+ notification.Meta.ShortSha,
+ notification.Meta.CommitURL,
+ )
+ }
+ text = fmt.Sprintf("%s pushed in [%s](%s) *deployment skipped*",
+ text,
+ notification.Meta.RepoFullName,
+ notification.Meta.RepoURL,
+ )
+ case "deployEnvironment":
+ text = fmt.Sprintf("Deployment triggered `%s`",
+ notification.Meta.BranchName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text = fmt.Sprintf("%s (%s)",
+ text,
+ notification.Meta.ShortSha,
+ )
+ }
+ case "removeFinished":
+ text = fmt.Sprintf("Removed `%s`",
+ notification.Meta.OpenshiftProject,
+ )
+ case "removeRetry":
+ text = fmt.Sprintf("Removed `%s`",
+ notification.Meta.OpenshiftProject,
+ )
+ case "notDeleted":
+ text = fmt.Sprintf("`%s` not deleted. %s",
+ notification.Meta.BranchName,
+ notification.Meta.Error,
+ )
+ case "deployError":
+ if notification.Meta.ShortSha != "" {
+ text += fmt.Sprintf("`%s` %s",
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ text += fmt.Sprintf(" `%s`",
+ notification.Meta.BranchName,
+ )
+ }
+ text += fmt.Sprintf(" Build `%s` Failed.",
+ notification.Meta.BuildName,
+ )
+ if notification.Meta.LogLink != "" {
+ text += fmt.Sprintf(" [Logs](%s) \r",
+ notification.Meta.LogLink,
+ )
+ }
+ case "deployFinished":
+ if notification.Meta.ShortSha != "" {
+ text += fmt.Sprintf("`%s` %s",
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ text += fmt.Sprintf("`%s`",
+ notification.Meta.BranchName,
+ )
+ }
+ text += fmt.Sprintf(" Build `%s` Succeeded.",
+ notification.Meta.BuildName,
+ )
+ if notification.Meta.LogLink != "" {
+ text += fmt.Sprintf(" [Logs](%s) \r",
+ notification.Meta.LogLink,
+ )
+ }
+ text += fmt.Sprintf("* %s \n",
+ notification.Meta.Route,
+ )
+ if len(notification.Meta.Routes) != 0 {
+ for _, r := range notification.Meta.Routes {
+ if r != notification.Meta.Route {
+ text += fmt.Sprintf("* %s \n", r)
+ }
+ }
+ }
+ default:
+ // do nothing
+ return
+ }
+
+ data := MicrosoftTeamsData{
+ Type: "MessageCard",
+ Context: "http://schema.org/extensions",
+ Summary: text,
+ Title: notification.Meta.ProjectName,
+ ThemeColor: color,
+ Sections: []MicrosoftTeamsSection{
+ {
+ ActivityText: text,
+ ActivityImage: emoji,
+ },
+ },
+ }
+
+ jsonBytes, _ := json.Marshal(data)
+ req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(jsonBytes))
+ req.Header.Set("Content-Type", "application/json")
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error sending message to rocketchat: %v", err)
+ return
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to rocketchat", notification.Event))
+}
+
+func getMicrosoftTeamsEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := microsoftTeamsEvent[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var microsoftTeamsEvent = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/rocketchat_events.go b/services/logs2notifications/internal/handler/rocketchat_events.go
index b8f0b8ce8f..1657a0d150 100644
--- a/services/logs2notifications/internal/handler/rocketchat_events.go
+++ b/services/logs2notifications/internal/handler/rocketchat_events.go
@@ -32,6 +32,9 @@ type RocketChatAttachmentField struct {
func SendToRocketChat(notification *Notification, channel, webhook, appID string) {
emoji, color, template, err := getRocketChatEvent(notification.Event)
+ if err != nil {
+ return
+ }
var text string
switch template {
@@ -63,7 +66,7 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
notification.Meta.RepoURL,
)
case "deleteEnvironment":
- text = fmt.Sprintf("*[%s]* delete trigger `%s`",
+ text = fmt.Sprintf("*[%s]* Deleting environment `%s`",
notification.Meta.ProjectName,
notification.Meta.EnvironmentName,
)
@@ -106,7 +109,7 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
notification.Meta.RepoURL,
)
case "deployEnvironment":
- text = fmt.Sprintf("*[%s]* API deploy trigger `%s`",
+ text = fmt.Sprintf("*[%s]* Deployment triggered `%s`",
notification.Meta.ProjectName,
notification.Meta.BranchName,
)
@@ -116,24 +119,97 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
notification.Meta.ShortSha,
)
}
- default:
- text = fmt.Sprintf("*[%s]* Event received for `%s`",
+ case "removeFinished":
+ text = fmt.Sprintf("*[%s]* Removed `%s`",
+ notification.Meta.ProjectName,
+ notification.Meta.OpenshiftProject,
+ )
+ case "removeRetry":
+ text = fmt.Sprintf("*[%s]* Removed `%s`",
+ notification.Meta.ProjectName,
+ notification.Meta.OpenshiftProject,
+ )
+ case "notDeleted":
+ text = fmt.Sprintf("*[%s]* `%s` not deleted. %s",
notification.Meta.ProjectName,
notification.Meta.BranchName,
+ notification.Meta.Error,
+ )
+ case "deployError":
+ text = fmt.Sprintf("*[%s]*",
+ notification.Meta.ProjectName,
)
+ if notification.Meta.ShortSha != "" {
+ text += fmt.Sprintf(" `%s` %s",
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ text += fmt.Sprintf(" `%s`",
+ notification.Meta.BranchName,
+ )
+ }
+ text += fmt.Sprintf(" Build `%s` Failed.",
+ notification.Meta.BuildName,
+ )
+ if notification.Meta.LogLink != "" {
+ text += fmt.Sprintf(" [Logs](%s) \r",
+ notification.Meta.LogLink,
+ )
+ }
+ case "deployFinished":
+ text = fmt.Sprintf("*[%s]*",
+ notification.Meta.ProjectName,
+ )
+ if notification.Meta.ShortSha != "" {
+ text += fmt.Sprintf(" `%s` %s",
+ notification.Meta.BranchName,
+ notification.Meta.ShortSha,
+ )
+ } else {
+ text += fmt.Sprintf(" `%s`",
+ notification.Meta.BranchName,
+ )
+ }
+ text += fmt.Sprintf(" Build `%s` Succeeded.",
+ notification.Meta.BuildName,
+ )
+ if notification.Meta.LogLink != "" {
+ text += fmt.Sprintf(" [Logs](%s) \r",
+ notification.Meta.LogLink,
+ )
+ }
+ text += fmt.Sprintf("* %s \n",
+ notification.Meta.Route,
+ )
+ if len(notification.Meta.Routes) != 0 {
+ for _, r := range notification.Meta.Routes {
+ if r != notification.Meta.Route {
+ text += fmt.Sprintf("* %s \n", r)
+ }
+ }
+ }
+ default:
+ // do nothing
+ return
}
data := RocketChatData{
Channel: channel,
- Attachments: []RocketChatAttachment{{
- Text: fmt.Sprintf("%s %s", emoji, text),
- Color: color,
- Fields: []RocketChatAttachmentField{{
- Short: true,
- Title: "Source",
- Value: appID,
- }},
- }},
+ Attachments: []RocketChatAttachment{
+ {
+ // Text: fmt.Sprintf("%s %s", emoji, notification.Message),
+ Text: fmt.Sprintf("%s %s", emoji, text),
+ Color: color,
+ Fields: []RocketChatAttachmentField{
+ {
+ Short: true,
+ Title: "Source",
+ Value: appID,
+ },
+ },
+ },
+ },
}
jsonBytes, _ := json.Marshal(data)
@@ -144,7 +220,8 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- panic(err)
+ log.Printf("Error sending message to rocketchat: %v", err)
+ return
}
defer resp.Body.Close()
log.Println(fmt.Sprintf("Sent %s message to rocketchat", notification.Event))
@@ -157,22 +234,16 @@ func getRocketChatEvent(msgEvent string) (string, string, string, error) {
return "", "", "", fmt.Errorf("no matching event source")
}
-type rocketChatEvent struct {
- Emoji string `json:"emoji"`
- Color string `json:"color"`
- Template string `json:"template"`
-}
-
-var rocketChatEventTypeMap = map[string]rocketChatEvent{
+var rocketChatEventTypeMap = map[string]EventMap{
"github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
"gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
"github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
"gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
"github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
"bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
@@ -180,9 +251,9 @@ var rocketChatEventTypeMap = map[string]rocketChatEvent{
"gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
"github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
- "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
- "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
- "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
"github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
"bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
@@ -198,30 +269,36 @@ var rocketChatEventTypeMap = map[string]rocketChatEvent{
"task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
"task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
"task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
- "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"}, //not in teams
"task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
"task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
- "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
-
- "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
"github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
"github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
"bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
"gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
- "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
- // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
}
diff --git a/services/logs2notifications/internal/handler/s3_events.go b/services/logs2notifications/internal/handler/s3_events.go
new file mode 100644
index 0000000000..f53ed93a5c
--- /dev/null
+++ b/services/logs2notifications/internal/handler/s3_events.go
@@ -0,0 +1,67 @@
+package handler
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "os"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/s3"
+)
+
+var (
+ AWS_S3_REGION = ""
+ AWS_S3_BUCKET = ""
+)
+
+func getS3Event(msgEvent string) (string, string, string, error) {
+ if val, ok := s3Event[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var s3Event = map[string]EventMap{
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+}
+
+// func main() {
+// session, err := session.NewSession(&aws.Config{Region: aws.String(AWS_S3_REGION)})
+// if err != nil {
+// log.Fatal(err)
+// }
+
+// // Upload Files
+// err = uploadFile(session, "test.png")
+// if err != nil {
+// log.Fatal(err)
+// }
+// }
+
+func uploadFile(session *session.Session, uploadFileDir string) error {
+
+ upFile, err := os.Open(uploadFileDir)
+ if err != nil {
+ return err
+ }
+ defer upFile.Close()
+
+ upFileInfo, _ := upFile.Stat()
+ var fileSize int64 = upFileInfo.Size()
+ fileBuffer := make([]byte, fileSize)
+ upFile.Read(fileBuffer)
+
+ _, err = s3.New(session).PutObject(&s3.PutObjectInput{
+ Bucket: aws.String(AWS_S3_BUCKET),
+ Key: aws.String(uploadFileDir),
+ ACL: aws.String("private"),
+ Body: bytes.NewReader(fileBuffer),
+ ContentLength: aws.Int64(fileSize),
+ ContentType: aws.String(http.DetectContentType(fileBuffer)),
+ ContentDisposition: aws.String("attachment"),
+ ServerSideEncryption: aws.String("AES256"),
+ })
+ return err
+}
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
index 12b02c3c71..6d153e60a1 100644
--- a/services/logs2notifications/internal/handler/slack_events.go
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -2,63 +2,110 @@ package handler
import (
"fmt"
+ "log"
+
+ "github.com/slack-go/slack"
)
-func getSlackEvent(msgEvent string) (string, string, error) {
- if val, ok := slackEventTypeMap[msgEvent]; ok {
- return val.Emoji, val.Color, nil
+// SendToSlack .
+func SendToSlack(notification *Notification, channel, webhook, appID string) {
+
+ emoji, color, _, err := getSlackEvent(notification.Event)
+ if err != nil {
+ return
}
- return "", "", fmt.Errorf("no matching event source")
+ attachment := slack.Attachment{
+ Text: fmt.Sprintf("%s %s", emoji, notification.Message),
+ Color: color,
+ Footer: appID,
+ MarkdownIn: []string{"pretext", "text", "fields"},
+ }
+ postMsg := slack.WebhookMessage{
+ Attachments: []slack.Attachment{attachment},
+ Channel: channel,
+ }
+
+ err = slack.PostWebhook(webhook, &postMsg)
+ if err != nil {
+ // just log any errors
+ log.Printf("Error sending message to slack: %v", err)
+ return
+ }
+ log.Println(fmt.Sprintf("Sent %s message to slack", notification.Event))
}
-type slackEvent struct {
- Emoji string `json:"emoji"`
- Color string `json:"color"`
+func getSlackEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := slackEventTypeMap[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
}
-var slackEventTypeMap = map[string]slackEvent{
- "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8"},
- "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "good"},
- "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "good"},
- "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "good"},
- "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "good"},
- "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "good"},
- "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "good"},
- "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "warning"},
- "task:remove-openshift:retry": {Emoji: ":warning:", Color: "warning"},
- "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "warning"},
- "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "warning"},
- "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "danger"},
- "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "danger"},
- "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "danger"},
- "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "danger"},
- "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "danger"},
- "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "danger"},
+var slackEventTypeMap = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
+ // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
}
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
index 1d2a6ded54..789201f56c 100644
--- a/services/logs2notifications/main.go
+++ b/services/logs2notifications/main.go
@@ -26,10 +26,19 @@ var (
lagoonAppID string
jwtTokenSigningKey string
jwtAudience string
- actionsQueueName string
- actionsExchange string
jwtSubject string
jwtIssuer string
+ s3FilesAccessKeyID string
+ s3FilesSecretAccessKey string
+ s3FilesBucket string
+ s3FilesRegion string
+ s3FilesOrigin string
+ disableSlack bool
+ disableRocketChat bool
+ disableMicrosoftTeams bool
+ disableEmail bool
+ disableWebhooks bool
+ disableS3 bool
)
func main() {
@@ -61,10 +70,28 @@ func main() {
"The jwt audience.")
flag.StringVar(&jwtIssuer, "jwt-issuer", "logs2notifications",
"The jwt audience.")
- flag.StringVar(&actionsQueueName, "actions-queue-name", "lagoon-actions:items",
- "The name of the queue in rabbitmq to use.")
- flag.StringVar(&actionsExchange, "actions-exchange", "lagoon-actions",
- "The name of the exchange in rabbitmq to use.")
+ flag.StringVar(&s3FilesAccessKeyID, "s3-files-access-key", "minio",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesSecretAccessKey, "s3-files-secret-access-key", "minio123",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesBucket, "s3-files-bucket", "lagoon-files",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesRegion, "s3-files-region", "",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesOrigin, "s3-files-origin", "http://docker.for.mac.localhost:9000",
+ "The jwt audience.")
+ flag.BoolVar(&disableSlack, "disable-slack", false,
+ "Disable the logs2slack feature.")
+ flag.BoolVar(&disableRocketChat, "disable-rocketchat", false,
+ "Disable the logs2rocketchat feature.")
+ flag.BoolVar(&disableMicrosoftTeams, "disable-microsoft-teams", false,
+ "Disable the logs2microsoftteams feature.")
+ flag.BoolVar(&disableEmail, "disable-email", false,
+ "Disable the logs2email feature.")
+ flag.BoolVar(&disableWebhooks, "disable-webhooks", false,
+ "Disable the logs2webhooks feature.")
+ flag.BoolVar(&disableS3, "disable-s3", false,
+ "Disable the logs2s3 feature.")
flag.Parse()
// get overrides from environment variables
@@ -77,18 +104,20 @@ func main() {
jwtAudience = getEnv("JWT_AUDIENCE", jwtAudience)
jwtSubject = getEnv("JWT_SUBJECT", jwtSubject)
jwtIssuer = getEnv("JWT_ISSUER", jwtIssuer)
- actionsQueueName = getEnv("ACTIONS_QUEUE_NAME", actionsQueueName)
- actionsExchange = getEnv("ACTIONS_EXCHANGE", actionsExchange)
+
+ s3FilesAccessKeyID = getEnv("S3_FILES_ACCESS_KEY_ID", s3FilesAccessKeyID)
+ s3FilesSecretAccessKey = getEnv("S3_FILES_SECRET_ACCESS_KEY", s3FilesSecretAccessKey)
+ s3FilesBucket = getEnv("S3_FILES_BUCKET", s3FilesBucket)
+ s3FilesRegion = getEnv("S3_FILES_REGION", s3FilesRegion)
+ s3FilesOrigin = getEnv("S3_FILES_HOST", s3FilesOrigin)
enableDebug := true
// configure the backup handler settings
broker := handler.RabbitBroker{
- Hostname: fmt.Sprintf("%s:%s", mqHost, mqPort),
- Username: mqUser,
- Password: mqPass,
- QueueName: actionsQueueName,
- ExchangeName: actionsExchange,
+ Hostname: fmt.Sprintf("%s:%s", mqHost, mqPort),
+ Username: mqUser,
+ Password: mqPass,
}
graphQLConfig := handler.LagoonAPI{
Endpoint: lagoonAPIHost,
@@ -160,6 +189,12 @@ func main() {
startupConnectionInterval,
enableDebug,
lagoonAppID,
+ disableSlack,
+ disableRocketChat,
+ disableMicrosoftTeams,
+ disableEmail,
+ disableWebhooks,
+ disableS3,
)
// start the consumer
From 49afad531e00ab8353ef2baa0f217fbc1aac7f7c Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 09:48:50 +1100
Subject: [PATCH 03/17] feat: add logs2notifications
---
services/logs2notifications/Dockerfile | 36 +++
services/logs2notifications/go.mod | 24 ++
services/logs2notifications/go.sum | 207 ++++++++++++
.../internal/handler/email_events.go | 284 +++++++++++++++++
.../internal/handler/main.go | 297 ++++++++++++++++++
.../internal/handler/main_test.go | 209 ++++++++++++
.../internal/handler/microsoftteams_events.go | 201 ++++++++++++
.../internal/handler/rocketchat_events.go | 207 ++++++++++++
.../internal/handler/s3_events.go | 83 +++++
.../internal/handler/slack_events.go | 177 +++++++++++
.../testdata/deleteEnvironment/emailhtml.txt | 1 +
.../testdata/deleteEnvironment/emailplain.txt | 1 +
.../testdata/deleteEnvironment/rocketchat.txt | 1 +
.../testdata/deleteEnvironment/slack.txt | 1 +
.../testdata/deleteEnvironment/teams.txt | 1 +
.../testdata/deployEnvironment/emailhtml.txt | 1 +
.../testdata/deployEnvironment/emailplain.txt | 1 +
.../testdata/deployEnvironment/rocketchat.txt | 1 +
.../testdata/deployEnvironment/slack.txt | 1 +
.../testdata/deployEnvironment/teams.txt | 1 +
.../testdata/deployError/emailhtml.txt | 2 +
.../testdata/deployError/emailplain.txt | 2 +
.../testdata/deployError/rocketchat.txt | 1 +
.../handler/testdata/deployError/slack.txt | 1 +
.../handler/testdata/deployError/teams.txt | 1 +
.../handler/testdata/deployError/webhook.txt | 1 +
.../testdata/deployFinished/emailhtml.txt | 10 +
.../testdata/deployFinished/emailplain.txt | 4 +
.../testdata/deployFinished/rocketchat.txt | 4 +
.../handler/testdata/deployFinished/slack.txt | 4 +
.../handler/testdata/deployFinished/teams.txt | 4 +
.../testdata/deployFinished/webhook.txt | 1 +
.../input.deleteEnvironmentGithub.json | 8 +
.../testdata/input.deployEnvironment.json | 9 +
.../handler/testdata/input.deployError.json | 11 +
.../testdata/input.deployFinished.json | 14 +
.../testdata/input.repoPushHandledGithub.json | 12 +
.../testdata/input.repoPushSkippedGithub.json | 12 +
.../handler/testdata/mergeRequestClosed.json | 11 +
.../handler/testdata/mergeRequestOpened.json | 11 +
.../handler/testdata/mergeRequestUpdated.json | 11 +
.../internal/handler/testdata/notDeleted.json | 0
.../testdata/problemNotification.1.json | 12 +
.../testdata/problemNotification.2.json | 12 +
.../handler/testdata/removeFinished.json | 0
.../testdata/repoPushHandled/emailhtml.txt | 1 +
.../testdata/repoPushHandled/emailplain.txt | 1 +
.../testdata/repoPushHandled/rocketchat.txt | 1 +
.../testdata/repoPushHandled/slack.txt | 1 +
.../testdata/repoPushHandled/teams.txt | 1 +
.../testdata/repoPushSkipped/emailhtml.txt | 1 +
.../testdata/repoPushSkipped/emailplain.txt | 1 +
.../testdata/repoPushSkipped/rocketchat.txt | 1 +
.../testdata/repoPushSkipped/slack.txt | 1 +
.../testdata/repoPushSkipped/teams.txt | 1 +
.../internal/handler/webhook_events.go | 150 +++++++++
.../internal/helpers/helpers.go | 77 +++++
.../internal/helpers/helpers_test.go | 160 ++++++++++
.../_lgraphql/projectNotifications.graphql | 41 +++
.../internal/lagoon/client/client.go | 93 ++++++
.../internal/lagoon/client/client_test.go | 81 +++++
.../internal/lagoon/client/helper_test.go | 6 +
.../lagoon/client/lgraphql/lgraphql.go | 247 +++++++++++++++
.../internal/lagoon/client/query.go | 25 ++
.../internal/lagoon/jwt/jwt.go | 30 ++
.../internal/lagoon/project.go | 20 ++
.../internal/schema/notifications.go | 107 +++++++
.../internal/schema/project.go | 8 +
services/logs2notifications/main.go | 259 +++++++++++++++
69 files changed, 3207 insertions(+)
create mode 100644 services/logs2notifications/Dockerfile
create mode 100644 services/logs2notifications/go.mod
create mode 100644 services/logs2notifications/go.sum
create mode 100644 services/logs2notifications/internal/handler/email_events.go
create mode 100644 services/logs2notifications/internal/handler/main.go
create mode 100644 services/logs2notifications/internal/handler/main_test.go
create mode 100644 services/logs2notifications/internal/handler/microsoftteams_events.go
create mode 100644 services/logs2notifications/internal/handler/rocketchat_events.go
create mode 100644 services/logs2notifications/internal/handler/s3_events.go
create mode 100644 services/logs2notifications/internal/handler/slack_events.go
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/webhook.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/webhook.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployError.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployFinished.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
create mode 100644 services/logs2notifications/internal/handler/testdata/notDeleted.json
create mode 100644 services/logs2notifications/internal/handler/testdata/problemNotification.1.json
create mode 100644 services/logs2notifications/internal/handler/testdata/problemNotification.2.json
create mode 100644 services/logs2notifications/internal/handler/testdata/removeFinished.json
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
create mode 100644 services/logs2notifications/internal/handler/webhook_events.go
create mode 100644 services/logs2notifications/internal/helpers/helpers.go
create mode 100644 services/logs2notifications/internal/helpers/helpers_test.go
create mode 100644 services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
create mode 100644 services/logs2notifications/internal/lagoon/client/client.go
create mode 100644 services/logs2notifications/internal/lagoon/client/client_test.go
create mode 100644 services/logs2notifications/internal/lagoon/client/helper_test.go
create mode 100644 services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
create mode 100644 services/logs2notifications/internal/lagoon/client/query.go
create mode 100644 services/logs2notifications/internal/lagoon/jwt/jwt.go
create mode 100644 services/logs2notifications/internal/lagoon/project.go
create mode 100644 services/logs2notifications/internal/schema/notifications.go
create mode 100644 services/logs2notifications/internal/schema/project.go
create mode 100644 services/logs2notifications/main.go
diff --git a/services/logs2notifications/Dockerfile b/services/logs2notifications/Dockerfile
new file mode 100644
index 0000000000..49ee341fbb
--- /dev/null
+++ b/services/logs2notifications/Dockerfile
@@ -0,0 +1,36 @@
+# build the binary
+ARG GO_VERSION
+ARG UPSTREAM_REPO
+ARG UPSTREAM_TAG
+FROM golang:${GO_VERSION:-1.16.4} AS builder
+# bring in all the packages
+COPY . /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
+WORKDIR /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
+
+# tests currently don't work because mocking rabbit is interesting
+RUN GO111MODULE=on go test ./...
+# compile
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o logs2notifications .
+
+# put the binary into container
+# use the commons image to get entrypoints
+FROM ${UPSTREAM_REPO:-uselagoon}/commons:${UPSTREAM_TAG:-latest}
+
+ARG LAGOON_VERSION
+ENV LAGOON_VERSION=$LAGOON_VERSION
+
+WORKDIR /app/
+
+# bring the auto-idler binary from the builder
+COPY --from=builder /go/src/github.com/uselagoon/lagoon/services/logs2notifications/logs2notifications .
+
+ENV LAGOON=logs2notifications
+# set defaults
+ENV JWT_SECRET=super-secret-string \
+ JWT_AUDIENCE=api.dev \
+ GRAPHQL_ENDPOINT="http://api:3000/graphql" \
+ RABBITMQ_ADDRESS=broker \
+ RABBITMQ_PORT=5672 \
+ RABBITMQ_USERNAME=guest \
+ RABBITMQ_PASSWORD=guest
+CMD ["/app/logs2notifications"]
\ No newline at end of file
diff --git a/services/logs2notifications/go.mod b/services/logs2notifications/go.mod
new file mode 100644
index 0000000000..4382a0c4c7
--- /dev/null
+++ b/services/logs2notifications/go.mod
@@ -0,0 +1,24 @@
+module github.com/uselagoon/lagoon/services/logs2notifications
+
+go 1.16
+
+require (
+ github.com/aws/aws-sdk-go v1.41.11
+ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
+ github.com/cheshir/go-mq v1.0.2
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/fsouza/go-dockerclient v1.7.3 // indirect
+ github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225
+ github.com/matryer/is v1.4.0 // indirect
+ github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2
+ github.com/slack-go/slack v0.9.5
+ github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gopkg.in/mail.v2 v2.3.1 // indirect
+)
+
+// Fixes for AppID
+replace github.com/cheshir/go-mq v1.0.2 => github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead
+
+replace github.com/NeowayLabs/wabbit v0.0.0-20200409220312-12e68ab5b0c6 => github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204
diff --git a/services/logs2notifications/go.sum b/services/logs2notifications/go.sum
new file mode 100644
index 0000000000..7cbfa00037
--- /dev/null
+++ b/services/logs2notifications/go.sum
@@ -0,0 +1,207 @@
+bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
+github.com/aws/aws-sdk-go v1.41.11 h1:QLouWsiYQ8i22kD8k58Dpdhio1A0MpT7bg9ZNXqEjuI=
+github.com/aws/aws-sdk-go v1.41.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
+github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
+github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
+github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
+github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
+github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
+github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
+github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
+github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
+github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/fsouza/go-dockerclient v1.7.3/go.mod h1:8xfZB8o9SptLNJ13VoV5pMiRbZGWkU/Omu5VOu/KC9Y=
+github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225 h1:guHWmqIKr4G+gQ4uYU5vcZjsUhhklRA2uOcGVfcfqis=
+github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o=
+github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
+github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
+github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
+github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead h1:brBqfI3SWHkBhydQ4zdYbeQj/4Rq68GHO+Me8W7Fji8=
+github.com/shreddedbacon/go-mq v0.0.0-20200419104937-b8e9af912ead/go.mod h1:lAwW/xhfO27t6WSVHFtLdgYioymwJvQxMSH19z00/BY=
+github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204 h1:jXml7E4X/d9v6LATMXFPCF1yW6TKrs+o5wMtYTaBdTw=
+github.com/shreddedbacon/wabbit v0.0.0-20200419104837-5b7b769d7204/go.mod h1:l7t6U3j3uZUYroWctp1FvWEktRMuGqx2zCcb5cd8cS8=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/slack-go/slack v0.9.5 h1:j7uOUDowybWf9eSgZg/AbGx6J1OPJB6SE8Z5dNl6Mtw=
+github.com/slack-go/slack v0.9.5/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
+github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71 h1:2MR0pKUzlP3SGgj5NYJe/zRYDwOu9ku6YHy+Iw7l5DM=
+github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218/go.mod h1:GQei++1WClbEC7AN1B9ipY1jCjzllM/7UNg0okAh/Z4=
+github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/uselagoon/lagoon v1.15.1 h1:/EUJJrWmmNrFVIh4vYm71/+fk2yXlCk+M5g8L+2ll0c=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
+gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
+gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/services/logs2notifications/internal/handler/email_events.go b/services/logs2notifications/internal/handler/email_events.go
new file mode 100644
index 0000000000..8004ddb21d
--- /dev/null
+++ b/services/logs2notifications/internal/handler/email_events.go
@@ -0,0 +1,284 @@
+package handler
+
+import (
+ "bytes"
+ "crypto/tls"
+ "fmt"
+ "log"
+ "strconv"
+ "strings"
+ "text/template"
+
+ gomail "gopkg.in/mail.v2"
+)
+
+var htmlTemplate = `
+
+
+ {{.Title}}
+
+
+
+
+
+
+
+
{{.Emoji}} [{{.ProjectName}}]
+
+ {{.MainHTML}}
+
+
+
+`
+
+// SendToEmail .
+func (h *Messaging) SendToEmail(notification *Notification, emailAddress string) {
+ emoji, color, subject, mainHTML, plainText, err := h.processEmailTemplates(notification)
+ if err != nil {
+ return
+ }
+ h.sendEmailMessage(emoji, color, subject, notification.Event, notification.Meta.ProjectName, emailAddress, mainHTML, plainText)
+}
+
+// SendToEmail .
+func (h *Messaging) processEmailTemplates(notification *Notification) (string, string, string, string, string, error) {
+
+ emoji, color, tpl, err := getEmailEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ fmt.Println(eventSplit[0])
+ if eventSplit[0] != "problem" {
+ return "", "", "", "", "", nil
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+ var mainHTML, plainText, subject, plainTextTpl, mainHTMLTpl string
+ switch tpl {
+ case "mergeRequestOpened":
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) opened in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} opened in {{.RepoName}}`
+ case "mergeRequestUpdated":
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) updated in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} updated in {{.RepoName}}`
+ case "mergeRequestClosed":
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) closed in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} closed in {{.RepoName}}`
+ case "deleteEnvironment":
+ mainHTMLTpl = `Deleted environment {{.EnvironmentName}}
`
+ plainTextTpl = `[{{.ProjectName}}] deleted environment {{.EnvironmentName}}`
+ case "repoPushHandled":
+ mainHTMLTpl = `{{.BranchName}}{{ if ne .ShortSha "" }} {{.ShortSha}}{{end}} pushed in {{.RepoFullName}}`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} pushed in {{.RepoFullName}}`
+ case "repoPushSkipped":
+ mainHTMLTpl = `{{.BranchName}}{{ if ne .ShortSha "" }} {{.ShortSha}}{{end}} pushed in {{.RepoFullName}} deployment skipped`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} pushed in {{.RepoFullName}} *deployment skipped*`
+ case "deployEnvironment":
+ mainHTMLTpl = `Deployment triggered {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ plainTextTpl = `[{{.ProjectName}}] Deployment triggered on branch {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ case "removeFinished":
+ mainHTMLTpl = `Remove {{.OpenshiftProject}}
`
+ plainTextTpl = `[{{.ProjectName}] remove {{.OpenshiftProject}}`
+ case "notDeleted":
+ mainHTMLTpl = `{{.OpenshiftProject}}
not deleted.`
+ plainTextTpl = `[{{.ProjectName}] {{.OpenshiftProject}} not deleted. {{.Error}}`
+ case "deployError":
+ mainHTMLTpl = `[{{.ProjectName}}] {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}}
error.
+{{if ne .LogLink ""}} Logs{{end}}`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}} error.
+{{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
+ subject += fmt.Sprintf("[%s] %s Build %s error.",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ notification.Meta.BuildName,
+ )
+ case "deployFinished":
+ mainHTMLTpl = `[{{.ProjectName}}] {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}}
complete. {{if ne .LogLink ""}}Logs{{end}}
+
+
+
+
+
`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}} complete. {{if ne .LogLink ""}}[Logs]({{.LogLink}}){{end}}
+{{.Route}}
+{{range .Routes}}{{if ne . $.Route}}{{.}}
+{{end}}{{end}}`
+ subject += fmt.Sprintf("[%s] %s Build %s complete.",
+ notification.Meta.ProjectName,
+ notification.Meta.BranchName,
+ notification.Meta.BuildName,
+ )
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", "", "", nil
+ }
+ mainHTMLTpl = `[{{.ProjectName}}] New problem found for
{{.EnvironmentName}}
+
- * Service: {{.ServiceName}}
{{ if ne .Severity "" }}
+ - * Severity: {{.Severity}}{{end}}
{{ if ne .Description "" }}
+ - * Description: {{.Description}}
{{end}}
`
+ plainTextTpl = `[{{.ProjectName}}] New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ subject += fmt.Sprintf("[%s] New problem found for environment %s",
+ notification.Meta.ProjectName,
+ notification.Meta.EnvironmentName,
+ )
+ default:
+ return "", "", "", "", "", nil
+ }
+
+ var body bytes.Buffer
+ t, _ := template.New("email").Parse(mainHTMLTpl)
+ t.Execute(&body, notification.Meta)
+ mainHTML += body.String()
+
+ var plainTextBuffer bytes.Buffer
+ t, _ = template.New("email").Parse(plainTextTpl)
+ t.Execute(&plainTextBuffer, notification.Meta)
+ plainText += plainTextBuffer.String()
+ if subject == "" {
+ subject = plainText
+ }
+ return emoji, color, subject, mainHTML, plainText, nil
+}
+
+func (h *Messaging) sendEmailMessage(emoji, color, subject, event, project, emailAddress, mainHTML, plainText string) {
+ var body bytes.Buffer
+
+ // mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
+ // body.Write([]byte(fmt.Sprintf("From: Lagoon Notifications<%s>\nSubject: %s \n%s\n\n", h.EmailSender, subject, mimeHeaders)))
+
+ t, _ := template.New("email").Parse(htmlTemplate)
+ t.Execute(&body, struct {
+ Color string
+ Emoji string
+ Title string
+ ProjectName string
+ MainHTML string
+ }{
+ Title: plainText,
+ Color: color,
+ Emoji: emoji,
+ ProjectName: project,
+ MainHTML: mainHTML,
+ })
+
+ m := gomail.NewMessage()
+ m.SetHeader("From", h.EmailSender)
+ m.SetHeader("To", emailAddress)
+ m.SetHeader("Subject", subject)
+ m.SetBody("text/plain", plainText)
+ m.AddAlternative("text/html", body.String())
+ sPort, _ := strconv.Atoi(h.EmailPort)
+ d := gomail.NewDialer(h.EmailHost, sPort, h.EmailSender, "")
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: h.EmailInsecureSkipVerify}
+ if err := d.DialAndSend(m); err != nil {
+ fmt.Println(err)
+ panic(err)
+ }
+
+ // // Create authentication
+ // auth := smtp.PlainAuth("", sender, password, smtpHost)
+ // // Send actual message
+ // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, sender, to, body.Bytes())
+ // if err != nil {
+ // log.Printf("Error sending message to email: %v", err)
+ // return
+ // }
+ log.Println(fmt.Sprintf("Sent %s message to email", event))
+}
+
+func getEmailEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := emailEvent[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var emailEvent = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "‼️", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "‼️", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "‼️", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
new file mode 100644
index 0000000000..5ce74d3655
--- /dev/null
+++ b/services/logs2notifications/internal/handler/main.go
@@ -0,0 +1,297 @@
+package handler
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "regexp"
+ "time"
+
+ "github.com/cheshir/go-mq"
+ "github.com/matryer/try"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon"
+ lclient "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/jwt"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// RabbitBroker .
+type RabbitBroker struct {
+ Hostname string `json:"hostname"`
+ Port string `json:"port"`
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+}
+
+// LagoonAPI .
+type LagoonAPI struct {
+ Endpoint string `json:"endpoint"`
+ JWTAudience string `json:"audience"`
+ TokenSigningKey string `json:"tokenSigningKey`
+ JWTSubject string `json:"subject"`
+ JWTIssuer string `json:"issuer"`
+}
+
+// Action is the structure of an action that is received via the message queue.
+type Action struct {
+ Type string `json:"type"` // defines the action type
+ EventType string `json:"eventType"` // defines the eventtype field in the event notification
+ Data map[string]interface{} `json:"data"` // contains the payload for the action, this could be any json so using a map
+}
+
+type messaging interface {
+ Consumer()
+ Publish(string, []byte)
+}
+
+// Messaging is used for the config and client information for the messaging queue.
+type Messaging struct {
+ Config mq.Config
+ LagoonAPI LagoonAPI
+ ConnectionAttempts int
+ ConnectionRetryInterval int
+ EnableDebug bool
+ LagoonAppID string
+ DisableSlack bool
+ DisableRocketChat bool
+ DisableMicrosoftTeams bool
+ DisableEmail bool
+ DisableWebhooks bool
+ DisableS3 bool
+ EmailSender string
+ EmailSenderPassword string
+ EmailHost string
+ EmailPort string
+ EmailInsecureSkipVerify bool
+ S3FilesAccessKeyID string
+ S3FilesSecretAccessKey string
+ S3FilesBucket string
+ S3FilesRegion string
+ S3FilesOrigin string
+}
+
+// Notification .
+type Notification struct {
+ Severity string `json:"severity"`
+ Project string `json:"project"`
+ UUID string `json:"uuid"`
+ Event string `json:"event"`
+ Meta struct {
+ User struct {
+ ID string `json:"id"`
+ PreferredUsername string `json:"preferred_username"`
+ Email string `json:"email"`
+ } `json:"user"`
+ Headers struct {
+ UserAgent string `json:"user-agent"`
+ ContentType string `json:"content-type"`
+ ContentLength string `json:"content-length"`
+ Host string `json:"host"`
+ IPAddress string `json:"ipAddress"`
+ } `json:"headers"`
+ Project string `json:"project"`
+ ProjectName string `json:"projectName"`
+ BranchName string `json:"branchName`
+ Event string `json:"event"`
+ Level string `json:"level"`
+ Message string `json:"message"`
+ Timestamp string `json:"timestamp"`
+ ShortSha string `json:"shortSha"`
+ BuildName string `json:"buildName"`
+ CommitURL string `json:"commitUrl"`
+ Environment string `json:"environment"`
+ EnvironmentID string `json:"environmentId"`
+ EnvironmentName string `json:"environmentName"`
+ ServiceName string `json:"serviceName"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Error string `json:"error"`
+ JobName string `json:"jobName"`
+ LogLink string `json:"logLink"`
+ Name string `json:"name"`
+ OpenshiftProject string `json:"openshiftProject"`
+ PromoteSourceEnvironment string `json:"promoteSourceEnvironment"`
+ PullrequestNumber string `json:"pullrequestNumber"`
+ PullrequestTitle string `json:"pullrequestTitle"`
+ PullrequestURL string `json:"pullrequestUrl"`
+ RemoteID string `json:"remoteId"`
+ RepoFullName string `json:"repoFullName"`
+ RepoName string `json:"repoName"`
+ RepoURL string `json:"repoUrl"`
+ Route string `json:"route"`
+ Routes []string `json:"routes"`
+ Task struct {
+ ID int `json:"id"`
+ } `json:"task"`
+ } `json:"meta"`
+ Message string `json:"message"`
+}
+
+// EventMap .
+type EventMap struct {
+ Emoji string `json:"emoji"`
+ Color string `json:"color"`
+ Template string `json:"template"`
+}
+
+// NewMessaging returns a messaging with config
+func NewMessaging(config mq.Config,
+ lagoonAPI LagoonAPI,
+ startupAttempts int,
+ startupInterval int,
+ enableDebug bool,
+ appID string,
+ disableSlack, disableRocketChat, disableMicrosoftTeams, disableEmail, disableWebhooks, disableS3 bool,
+ emailSender, emailSenderPassword, emailHost, emailPort string, emailInsecureSkipVerify bool,
+ s3FilesAccessKeyID, s3FilesSecretAccessKey, s3FilesBucket, s3FilesRegion, s3FilesOrigin string) *Messaging {
+ return &Messaging{
+ Config: config,
+ LagoonAPI: lagoonAPI,
+ ConnectionAttempts: startupAttempts,
+ ConnectionRetryInterval: startupInterval,
+ EnableDebug: enableDebug,
+ LagoonAppID: appID,
+ DisableSlack: disableSlack,
+ DisableRocketChat: disableRocketChat,
+ DisableMicrosoftTeams: disableMicrosoftTeams,
+ DisableEmail: disableEmail,
+ DisableWebhooks: disableWebhooks,
+ DisableS3: disableS3,
+ EmailSender: emailSender,
+ EmailSenderPassword: emailSenderPassword,
+ EmailHost: emailHost,
+ EmailPort: emailPort,
+ EmailInsecureSkipVerify: emailInsecureSkipVerify,
+ S3FilesAccessKeyID: s3FilesAccessKeyID,
+ S3FilesSecretAccessKey: s3FilesSecretAccessKey,
+ S3FilesBucket: s3FilesBucket,
+ S3FilesRegion: s3FilesRegion,
+ S3FilesOrigin: s3FilesOrigin,
+ }
+}
+
+// Consumer handles consuming messages sent to the queue that this action handler is connected to and processes them accordingly
+func (h *Messaging) Consumer() {
+
+ var messageQueue mq.MQ
+ // if no mq is found when the goroutine starts, retry a few times before exiting
+ // default is 10 retry with 30 second delay = 5 minutes
+ err := try.Do(func(attempt int) (bool, error) {
+ var err error
+ messageQueue, err = mq.New(h.Config)
+ if err != nil {
+ log.Println(err,
+ fmt.Sprintf(
+ "Failed to initialize message queue manager, retrying in %d seconds, attempt %d/%d",
+ h.ConnectionRetryInterval,
+ attempt,
+ h.ConnectionAttempts,
+ ),
+ )
+ time.Sleep(time.Duration(h.ConnectionRetryInterval) * time.Second)
+ }
+ return attempt < h.ConnectionAttempts, err
+ })
+ if err != nil {
+ log.Fatalf("Finally failed to initialize message queue manager: %v", err)
+ }
+ defer messageQueue.Close()
+
+ go func() {
+ for err := range messageQueue.Error() {
+ log.Println(fmt.Sprintf("Caught error from message queue: %v", err))
+ }
+ }()
+
+ forever := make(chan bool)
+
+ // Handle any tasks that go to the queue
+ log.Println("Listening for messages in queue lagoon-logs:notifications")
+ err = messageQueue.SetConsumerHandler("notifications-queue", func(message mq.Message) {
+ h.processMessage(message.Body(), message.AppId())
+ message.Ack(false) // ack to remove from queue
+ })
+ if err != nil {
+ log.Println(fmt.Sprintf("Failed to set handler to consumer `%s`: %v", "items-queue", err))
+ }
+ <-forever
+}
+
+func (h *Messaging) processMessage(message []byte, applicationID string) {
+ ctx := context.Background()
+ notification := &Notification{}
+ json.Unmarshal(message, notification)
+
+ var buildLogs = regexp.MustCompile(`^build-logs:builddeploy-kubernetes:.*`)
+ var taskLogs = regexp.MustCompile(`^(build|task)-logs:job-kubernetes:.*`)
+ switch notification.Event {
+ case buildLogs.FindString(notification.Event):
+ // if this is a build logs message handle it accordingly
+ if !h.DisableS3 {
+ h.SendToS3(notification, buildMessageType)
+ }
+ case taskLogs.FindString(notification.Event):
+ // if this is a task logs message handle it accordingly
+ if !h.DisableS3 {
+ h.SendToS3(notification, taskMessageType)
+ }
+ default:
+ // all other events are notifications, so do notification handling with them
+ // and as long as the event is not a `user_action` (activity logger)
+ if notification.Project != "" && notification.Meta.Level != "user_action" {
+ // marshal unmarshal the data into the input we need to use when talking to the lagoon api
+ projectNotifications, err := h.getProjectNotifictions(ctx, notification.Project)
+ if err != nil {
+ log.Println(err)
+ break
+ }
+ if projectNotifications.Notifications != nil {
+ if len(projectNotifications.Notifications.Slack) > 0 && !h.DisableSlack {
+ for _, slack := range projectNotifications.Notifications.Slack {
+ h.SendToSlack(notification, slack.Channel, slack.Webhook, applicationID)
+ }
+ }
+ if len(projectNotifications.Notifications.RocketChat) > 0 && !h.DisableRocketChat {
+ for _, rc := range projectNotifications.Notifications.RocketChat {
+ h.SendToRocketChat(notification, rc.Channel, rc.Webhook, applicationID)
+ }
+ }
+ if len(projectNotifications.Notifications.Email) > 0 && !h.DisableEmail {
+ for _, email := range projectNotifications.Notifications.Email {
+ h.SendToEmail(notification, email.EmailAddress)
+ }
+ }
+ if len(projectNotifications.Notifications.MicrosoftTeams) > 0 && !h.DisableMicrosoftTeams {
+ for _, teams := range projectNotifications.Notifications.MicrosoftTeams {
+ h.SendToMicrosoftTeams(notification, teams.Webhook)
+ }
+ }
+ if len(projectNotifications.Notifications.Webhook) > 0 && !h.DisableWebhooks {
+ for _, hook := range projectNotifications.Notifications.Webhook {
+ h.SendToWebhook(notification, hook)
+ }
+ }
+ }
+ }
+ }
+}
+
+func (h *Messaging) getProjectNotifictions(ctx context.Context, projectName string) (*schema.Project, error) {
+ token, err := jwt.OneMinuteAdminToken(h.LagoonAPI.TokenSigningKey, h.LagoonAPI.JWTAudience, h.LagoonAPI.JWTSubject, h.LagoonAPI.JWTIssuer)
+ if err != nil {
+ // the token wasn't generated
+ if h.EnableDebug {
+ log.Println(err)
+ }
+ return nil, err
+ }
+ // get all notifications for said project
+ l := lclient.New(h.LagoonAPI.Endpoint, token, "logs2notifications", false)
+ projectNotifications, err := lagoon.NotificationsForProject(ctx, projectName, l)
+ if err != nil {
+ log.Println(err)
+ return nil, err
+ }
+ return projectNotifications, nil
+}
diff --git a/services/logs2notifications/internal/handler/main_test.go b/services/logs2notifications/internal/handler/main_test.go
new file mode 100644
index 0000000000..29687731ca
--- /dev/null
+++ b/services/logs2notifications/internal/handler/main_test.go
@@ -0,0 +1,209 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "reflect"
+ "testing"
+
+ "github.com/cheshir/go-mq"
+)
+
+func checkEqual(t *testing.T, got, want interface{}, msgs ...interface{}) {
+ if !reflect.DeepEqual(got, want) {
+ buf := bytes.Buffer{}
+ buf.WriteString("got:\n[%v]\nwant:\n[%v]\n")
+ for _, v := range msgs {
+ buf.WriteString(v.(string))
+ }
+ t.Errorf(buf.String(), got, want)
+ }
+}
+
+func TestProcessing(t *testing.T) {
+ config := mq.Config{}
+ graphQLConfig := LagoonAPI{
+ // Endpoint: svr.URL,
+ // TokenSigningKey: "jwtTokenSigningKey",
+ // JWTAudience: "jwtAudience",
+ // JWTSubject: "jwtSubject",
+ // JWTIssuer: "jwtIssuer",
+ }
+ messaging := NewMessaging(config,
+ graphQLConfig,
+ 1,
+ 1,
+ true,
+ "lagoonAppID",
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ "emailSender",
+ "emailSenderPassword",
+ "emailHost",
+ "emailPort",
+ true,
+ "s3FilesAccessKeyID",
+ "s3FilesSecretAccessKey",
+ "s3FilesBucket",
+ "s3FilesRegion",
+ "s3FilesOrigin",
+ )
+ var testCases = map[string]struct {
+ input string
+ description string
+ slack string
+ rocketchat string
+ emailhtml string
+ emailplain string
+ teams string
+ webhook string
+ }{
+ "repoPushHandledGithub": {
+ description: "test github repo push handled events",
+ input: "testdata/input.repoPushHandledGithub.json",
+ slack: "testdata/repoPushHandled/slack.txt",
+ rocketchat: "testdata/repoPushHandled/rocketchat.txt",
+ emailhtml: "testdata/repoPushHandled/emailhtml.txt",
+ emailplain: "testdata/repoPushHandled/emailplain.txt",
+ teams: "testdata/repoPushHandled/teams.txt",
+ },
+ "repoPushSkippedGithub": {
+ description: "test github repo push skipped events",
+ input: "testdata/input.repoPushSkippedGithub.json",
+ slack: "testdata/repoPushSkipped/slack.txt",
+ rocketchat: "testdata/repoPushSkipped/rocketchat.txt",
+ emailhtml: "testdata/repoPushSkipped/emailhtml.txt",
+ emailplain: "testdata/repoPushSkipped/emailplain.txt",
+ teams: "testdata/repoPushSkipped/teams.txt",
+ },
+ "deleteEnvironmentGithub": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deleteEnvironmentGithub.json",
+ slack: "testdata/deleteEnvironment/slack.txt",
+ rocketchat: "testdata/deleteEnvironment/rocketchat.txt",
+ emailhtml: "testdata/deleteEnvironment/emailhtml.txt",
+ emailplain: "testdata/deleteEnvironment/emailplain.txt",
+ teams: "testdata/deleteEnvironment/teams.txt",
+ },
+ "deployEnvironment": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployEnvironment.json",
+ slack: "testdata/deployEnvironment/slack.txt",
+ rocketchat: "testdata/deployEnvironment/rocketchat.txt",
+ emailhtml: "testdata/deployEnvironment/emailhtml.txt",
+ emailplain: "testdata/deployEnvironment/emailplain.txt",
+ teams: "testdata/deployEnvironment/teams.txt",
+ },
+ "deployError": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployError.json",
+ slack: "testdata/deployError/slack.txt",
+ rocketchat: "testdata/deployError/rocketchat.txt",
+ emailhtml: "testdata/deployError/emailhtml.txt",
+ emailplain: "testdata/deployError/emailplain.txt",
+ teams: "testdata/deployError/teams.txt",
+ webhook: "testdata/deployError/webhook.txt",
+ },
+ "deployFinished": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployFinished.json",
+ slack: "testdata/deployFinished/slack.txt",
+ rocketchat: "testdata/deployFinished/rocketchat.txt",
+ emailhtml: "testdata/deployFinished/emailhtml.txt",
+ emailplain: "testdata/deployFinished/emailplain.txt",
+ teams: "testdata/deployFinished/teams.txt",
+ webhook: "testdata/deployFinished/webhook.txt",
+ },
+ }
+ for name, tc := range testCases {
+ t.Run(name, func(tt *testing.T) {
+ // read the input into a the notification struct
+ inputBytes, err := ioutil.ReadFile(tc.input) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ notification := &Notification{}
+ json.Unmarshal(inputBytes, notification)
+
+ // process slack template
+ resultBytes, err := ioutil.ReadFile(tc.slack) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err := messaging.processSlackTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ tt.Fatalf("message doesn't match, wanted `%s` got `%s`", message, string(resultBytes))
+ }
+
+ // process rocketchat template
+ resultBytes, err = ioutil.ReadFile(tc.rocketchat) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err = messaging.processRocketChatTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ tt.Fatalf("message doesn't match, wanted `%s` got `%s`", message, string(resultBytes))
+ }
+
+ // process email templates
+ resultBytesHTML, err := ioutil.ReadFile(tc.emailhtml) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ resultBytesPlainText, err := ioutil.ReadFile(tc.emailplain) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, _, htmlMessage, plaintextMessage, err := messaging.processEmailTemplates(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if htmlMessage != string(resultBytesHTML) {
+ tt.Fatalf("html doesn't match, wanted `%s` got `%s`", string(htmlMessage), string(resultBytes))
+ }
+ if plaintextMessage != string(resultBytesPlainText) {
+ tt.Fatalf("plaintextmessage doesn't match, wanted `%s` got `%s`", string(plaintextMessage), string(resultBytes))
+ }
+
+ // process teams template
+ resultBytes, err = ioutil.ReadFile(tc.teams) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err = messaging.processMicrosoftTeamsTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ tt.Fatalf("message doesn't match, wanted `%s` got `%s`", message, string(resultBytes))
+ }
+
+ // process webhook template
+ if tc.webhook != "" {
+ resultBytes, err = ioutil.ReadFile(tc.webhook) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ message, err := messaging.processWebhookTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ messageBytes, _ := json.Marshal(&message)
+ if string(messageBytes) != string(resultBytes) {
+ tt.Fatalf("message doesn't match, wanted `%s` got `%s`", string(messageBytes), string(resultBytes))
+ }
+ }
+ })
+ }
+}
diff --git a/services/logs2notifications/internal/handler/microsoftteams_events.go b/services/logs2notifications/internal/handler/microsoftteams_events.go
new file mode 100644
index 0000000000..87df357d1a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/microsoftteams_events.go
@@ -0,0 +1,201 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+ "text/template"
+)
+
+// MicrosoftTeamsData .
+type MicrosoftTeamsData struct {
+ Type string `json:"@type"`
+ Context string `json:"@context"`
+ Summary string `json:"summary"`
+ Title string `json:"title"`
+ ThemeColor string `json:"themeColor"`
+ Sections []MicrosoftTeamsSection `json:"sections"`
+}
+
+// MicrosoftTeamsSection .
+type MicrosoftTeamsSection struct {
+ ActivityText string `json:"activityText"`
+ ActivityImage string `json:"activityImage"`
+}
+
+// SendToMicrosoftTeams .
+func (h *Messaging) SendToMicrosoftTeams(notification *Notification, webhook string) {
+ emoji, color, message, err := h.processMicrosoftTeamsTemplate(notification)
+ if err != nil {
+ return
+ }
+ h.sendMicrosoftTeamsMessage(emoji, color, webhook, notification.Event, notification.Meta.ProjectName, message)
+}
+
+// processMicrosoftTeamsTemplate .
+func (h *Messaging) processMicrosoftTeamsTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getMicrosoftTeamsEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+
+ var teamsTpl string
+ switch tpl {
+ case "mergeRequestOpened":
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestUpdated":
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestClosed":
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
+ case "deleteEnvironment":
+ teamsTpl = `Deleting environment ` + "`{{.EnvironmentName}}`"
+ case "repoPushHandled":
+ teamsTpl = `[{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
+ case "repoPushSkipped":
+ teamsTpl = `[{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
+ case "deployEnvironment":
+ teamsTpl = `Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ case "removeFinished":
+ teamsTpl = `Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "removeRetry":
+ teamsTpl = `Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "notDeleted":
+ teamsTpl = "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
+ case "deployError":
+ teamsTpl = "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
+ case "deployFinished":
+ teamsTpl = "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ teamsTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ default:
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+
+ var teamsMsg bytes.Buffer
+ t, _ := template.New("microsoftteams").Parse(teamsTpl)
+ t.Execute(&teamsMsg, notification.Meta)
+ return emoji, color, teamsMsg.String(), nil
+}
+
+func (h *Messaging) sendMicrosoftTeamsMessage(emoji, color, webhook, event, project, message string) {
+ teamsPayload := MicrosoftTeamsData{
+ Type: "MessageCard",
+ Context: "http://schema.org/extensions",
+ Summary: message,
+ Title: project,
+ ThemeColor: color,
+ Sections: []MicrosoftTeamsSection{
+ {
+ ActivityText: message,
+ ActivityImage: emoji,
+ },
+ },
+ }
+
+ teamsPayloadBytes, _ := json.Marshal(teamsPayload)
+ req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(teamsPayloadBytes))
+ req.Header.Set("Content-Type", "application/json")
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error sending message to microsoft teams: %v", err)
+ return
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to microsoft teams", event))
+}
+
+func getMicrosoftTeamsEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := microsoftTeamsEvent[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var microsoftTeamsEvent = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/rocketchat_events.go b/services/logs2notifications/internal/handler/rocketchat_events.go
new file mode 100644
index 0000000000..978d0c723e
--- /dev/null
+++ b/services/logs2notifications/internal/handler/rocketchat_events.go
@@ -0,0 +1,207 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+ "text/template"
+)
+
+// RocketChatData .
+type RocketChatData struct {
+ Channel string `json:"channel"`
+ Attachments []RocketChatAttachment `json:"attachments"`
+}
+
+// RocketChatAttachment .
+type RocketChatAttachment struct {
+ Text string `json:"text"`
+ Color string `json:"color"`
+ Fields []RocketChatAttachmentField `json:"fields"`
+}
+
+// RocketChatAttachmentField .
+type RocketChatAttachmentField struct {
+ Short bool `json:"short"`
+ Title string `json:"title"`
+ Value string `json:"value"`
+}
+
+// SendToRocketChat .
+func (h *Messaging) SendToRocketChat(notification *Notification, channel, webhook, appID string) {
+ emoji, color, message, err := h.processRocketChatTemplate(notification)
+ if err != nil {
+ return
+ }
+ h.sendRocketChatMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+}
+
+// SendToRocketChat .
+func (h *Messaging) processRocketChatTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getRocketChatEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+
+ var rcTpl string
+ switch tpl {
+ case "mergeRequestOpened":
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestUpdated":
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestClosed":
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
+ case "deleteEnvironment":
+ rcTpl = `*[{{.ProjectName}}]* Deleting environment ` + "`{{.EnvironmentName}}`"
+ case "repoPushHandled":
+ rcTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
+ case "repoPushSkipped":
+ rcTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
+ case "deployEnvironment":
+ rcTpl = `*[{{.ProjectName}}]* Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ case "removeFinished":
+ rcTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "removeRetry":
+ rcTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "notDeleted":
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
+ case "deployError":
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
+ case "deployFinished":
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ rcTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ default:
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ var rcMsg bytes.Buffer
+ t, _ := template.New("rocketchat").Parse(rcTpl)
+ t.Execute(&rcMsg, notification.Meta)
+ return emoji, color, rcMsg.String(), nil
+}
+
+func (h *Messaging) sendRocketChatMessage(emoji, color, appID, channel, webhook, event, message string) {
+ data := RocketChatData{
+ Channel: channel,
+ Attachments: []RocketChatAttachment{
+ {
+ Text: fmt.Sprintf("%s %s", emoji, message),
+ Color: color,
+ Fields: []RocketChatAttachmentField{
+ {
+ Short: true,
+ Title: "Source",
+ Value: appID,
+ },
+ },
+ },
+ },
+ }
+ jsonBytes, _ := json.Marshal(data)
+ req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(jsonBytes))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Content-Length", fmt.Sprintf("%d", len(jsonBytes)))
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error sending message to rocketchat: %v", err)
+ return
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to rocketchat", event))
+}
+
+func getRocketChatEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := rocketChatEventTypeMap[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var rocketChatEventTypeMap = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "🛑", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "🛑", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/s3_events.go b/services/logs2notifications/internal/handler/s3_events.go
new file mode 100644
index 0000000000..c7f1d41416
--- /dev/null
+++ b/services/logs2notifications/internal/handler/s3_events.go
@@ -0,0 +1,83 @@
+package handler
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/credentials"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/s3"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/helpers"
+)
+
+// MessageType .
+type MessageType string
+
+const (
+ buildMessageType MessageType = "build"
+ taskMessageType MessageType = "task"
+)
+
+// SendToS3 .
+func (h *Messaging) SendToS3(notification *Notification, msgType MessageType) {
+ if msgType == buildMessageType {
+ h.uploadFileS3(
+ notification.Message,
+ fmt.Sprintf("buildlogs/%s/%s/%s-%s.txt",
+ notification.Project,
+ notification.Meta.BranchName,
+ notification.Meta.JobName,
+ notification.Meta.RemoteID,
+ ),
+ )
+ } else if msgType == taskMessageType {
+ filePath := fmt.Sprintf("tasklogs/%s/%d-%s.txt",
+ notification.Project,
+ notification.Meta.Task.ID,
+ notification.Meta.RemoteID,
+ )
+ if notification.Meta.Environment != "" {
+ filePath = fmt.Sprintf("tasklogs/%s/%s/%d-%s.txt",
+ notification.Project,
+ helpers.ShortenEnvironment(notification.Project, helpers.MakeSafe(notification.Meta.Environment)),
+ notification.Meta.Task.ID,
+ notification.Meta.RemoteID,
+ )
+
+ }
+ h.uploadFileS3(
+ notification.Message,
+ filePath,
+ )
+ }
+}
+
+// UploadFileS3
+func (h *Messaging) uploadFileS3(message, fileName string) {
+ var forcePath bool
+ forcePath = true
+ session, err := session.NewSession(&aws.Config{
+ Region: aws.String(h.S3FilesRegion),
+ Endpoint: aws.String(h.S3FilesOrigin),
+ Credentials: credentials.NewStaticCredentials(h.S3FilesAccessKeyID, h.S3FilesSecretAccessKey, ""),
+ S3ForcePathStyle: &forcePath,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ _, err = s3.New(session).PutObject(&s3.PutObjectInput{
+ Bucket: aws.String(h.S3FilesBucket),
+ Key: aws.String(fileName),
+ ACL: aws.String("private"),
+ Body: bytes.NewReader([]byte(message)),
+ ContentType: aws.String("text/plain"),
+ })
+ if err != nil {
+ log.Println(err)
+ }
+ log.Println(fmt.Sprintf("Uploaded file %s", fileName))
+ return
+}
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
new file mode 100644
index 0000000000..413fa75fb3
--- /dev/null
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -0,0 +1,177 @@
+package handler
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "strings"
+ "text/template"
+
+ "github.com/slack-go/slack"
+)
+
+// SendToSlack .
+func (h *Messaging) SendToSlack(notification *Notification, channel, webhook, appID string) {
+ emoji, color, message, err := h.processSlackTemplate(notification)
+ if err != nil {
+ return
+ }
+ h.sendSlackMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+}
+
+// processSlackTemplate .
+func (h *Messaging) processSlackTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getSlackEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+
+ var slackTpl string
+ switch tpl {
+ case "mergeRequestOpened":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestUpdated":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestClosed":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
+ case "deleteEnvironment":
+ slackTpl = `*[{{.ProjectName}}]* Deleting environment ` + "`{{.EnvironmentName}}`"
+ case "repoPushHandled":
+ slackTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
+ case "repoPushSkipped":
+ slackTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
+ case "deployEnvironment":
+ slackTpl = `*[{{.ProjectName}}]* Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ case "removeFinished":
+ slackTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "removeRetry":
+ slackTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "notDeleted":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
+ case "deployError":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}`
+ case "deployFinished":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ slackTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ default:
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+
+ var slackMsg bytes.Buffer
+ t, _ := template.New("slack").Parse(slackTpl)
+ t.Execute(&slackMsg, notification.Meta)
+ return emoji, color, slackMsg.String(), nil
+}
+
+func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, event, message string) {
+ attachment := slack.Attachment{
+ Text: fmt.Sprintf("%s %s", emoji, message),
+ Color: color,
+ Footer: appID,
+ MarkdownIn: []string{"pretext", "text", "fields"},
+ }
+ postMsg := slack.WebhookMessage{
+ Attachments: []slack.Attachment{attachment},
+ Channel: channel,
+ }
+
+ err := slack.PostWebhook(webhook, &postMsg)
+ if err != nil {
+ // just log any errors
+ log.Printf("Error sending message to slack: %v", err)
+ return
+ }
+ log.Println(fmt.Sprintf("Sent %s message to slack", event))
+}
+
+func getSlackEvent(msgEvent string) (string, string, string, error) {
+ if val, ok := slackEventTypeMap[msgEvent]; ok {
+ return val.Emoji, val.Color, val.Template, nil
+ }
+ return "", "", "", fmt.Errorf("no matching event source")
+}
+
+var slackEventTypeMap = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
new file mode 100644
index 0000000000..e4312f8960
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
@@ -0,0 +1 @@
+Deleted environment
lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
new file mode 100644
index 0000000000..6afbbb9a49
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] deleted environment lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
new file mode 100644
index 0000000000..ae882c10ee
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
new file mode 100644
index 0000000000..ae882c10ee
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
new file mode 100644
index 0000000000..7236b3ac99
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
@@ -0,0 +1 @@
+Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
new file mode 100644
index 0000000000..2df8b891e1
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
@@ -0,0 +1 @@
+Deployment triggered
lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
new file mode 100644
index 0000000000..a0d5c2f96a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] Deployment triggered on branch lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
new file mode 100644
index 0000000000..e38caf7ee5
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
new file mode 100644
index 0000000000..e38caf7ee5
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
new file mode 100644
index 0000000000..80e680018e
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
@@ -0,0 +1 @@
+Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
new file mode 100644
index 0000000000..caa0842373
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
@@ -0,0 +1,2 @@
+[ci-github-openshift]
lagoon-type-override
Build
lagoon-build-1234
error.
+
Logs
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
new file mode 100644
index 0000000000..6e842187b3
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
@@ -0,0 +1,2 @@
+[ci-github-openshift] lagoon-type-override Build lagoon-build-1234 error.
+ [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
new file mode 100644
index 0000000000..3188762d78
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Failed. [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/slack.txt b/services/logs2notifications/internal/handler/testdata/deployError/slack.txt
new file mode 100644
index 0000000000..cb1126de73
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Failed.
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/teams.txt b/services/logs2notifications/internal/handler/testdata/deployError/teams.txt
new file mode 100644
index 0000000000..ca2fa8daf9
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/teams.txt
@@ -0,0 +1 @@
+`lagoon-type-override` Build `lagoon-build-1234` Failed. [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/webhook.txt b/services/logs2notifications/internal/handler/testdata/deployError/webhook.txt
new file mode 100644
index 0000000000..5ecba91c37
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/webhook.txt
@@ -0,0 +1 @@
+{"type":"DEPLOYMENT","event":"task:builddeploy-kubernetes:failed","project":"ci-github-openshift","environment":"lagoon-type-override"}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
new file mode 100644
index 0000000000..26155d23c4
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
@@ -0,0 +1,10 @@
+[ci-github-openshift] lagoon-type-override
Build lagoon-build-1234
complete. Logs
+
+
+
+
+
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
new file mode 100644
index 0000000000..3364f45f8b
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
@@ -0,0 +1,4 @@
+[ci-github-openshift] lagoon-type-override Build lagoon-build-1234 complete. [Logs](https://logs)
+https://route1
+https://route2
+https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
new file mode 100644
index 0000000000..66f5f30f33
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
@@ -0,0 +1,4 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Succeeded. [Logs](https://logs)
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
new file mode 100644
index 0000000000..c2040f005a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
@@ -0,0 +1,4 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Succeeded.
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
new file mode 100644
index 0000000000..b68e460ad9
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
@@ -0,0 +1,4 @@
+`lagoon-type-override` Build `lagoon-build-1234` Succeeded. [Logs](https://logs)
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/webhook.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/webhook.txt
new file mode 100644
index 0000000000..4c9f31e748
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/webhook.txt
@@ -0,0 +1 @@
+{"type":"DEPLOYMENT","event":"task:deploy-openshift:finished","project":"ci-github-openshift","environment":"lagoon-type-override"}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json b/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
new file mode 100644
index 0000000000..b6df0dcfc5
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
@@ -0,0 +1,8 @@
+{
+ "project":"ci-github-openshift",
+ "event":"github:delete:handled",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json b/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
new file mode 100644
index 0000000000..ab2a1271c2
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
@@ -0,0 +1,9 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentLatest",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "project":"ci-github-openshift"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployError.json b/services/logs2notifications/internal/handler/testdata/input.deployError.json
new file mode 100644
index 0000000000..2127536769
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployError.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"task:builddeploy-kubernetes:failed",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "project":"ci-github-openshift",
+ "buildName":"lagoon-build-1234",
+ "logLink":"https://logs"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployFinished.json b/services/logs2notifications/internal/handler/testdata/input.deployFinished.json
new file mode 100644
index 0000000000..be4eb952fd
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployFinished.json
@@ -0,0 +1,14 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"task:deploy-openshift:finished",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "buildName":"lagoon-build-1234",
+ "route":"https://route1",
+ "routes":["https://route1","https://route2","https://route3"],
+ "logLink":"https://logs"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json b/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
new file mode 100644
index 0000000000..ef4ca09114
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
@@ -0,0 +1,12 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"github:push:handled",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "repoFullName":"owner/repository",
+ "repoURL":"github.com/owner/repository"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json b/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
new file mode 100644
index 0000000000..efcc4ccd5b
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
@@ -0,0 +1,12 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"github:push:skipped",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "repoFullName":"owner/repository",
+ "repoURL":"github.com/owner/repository"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json b/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json b/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json b/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/notDeleted.json b/services/logs2notifications/internal/handler/testdata/notDeleted.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/services/logs2notifications/internal/handler/testdata/problemNotification.1.json b/services/logs2notifications/internal/handler/testdata/problemNotification.1.json
new file mode 100644
index 0000000000..21fc0fe8c4
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/problemNotification.1.json
@@ -0,0 +1,12 @@
+{
+ "project":"ci-github-openshift",
+ "event":"problem:insert:source_name:summary:severity",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override",
+ "serviceName":"nginx",
+ "severity":"danger",
+ "description":"this is bad"
+ },
+ "message":"sagsdgasdgsdg"
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/problemNotification.2.json b/services/logs2notifications/internal/handler/testdata/problemNotification.2.json
new file mode 100644
index 0000000000..918ba09e82
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/problemNotification.2.json
@@ -0,0 +1,12 @@
+{
+ "project":"ci-github-openshift",
+ "event":"problem:update:source_name:summary:severity",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override",
+ "serviceName":"nginx",
+ "severity":"danger",
+ "description":"this is bad"
+ },
+ "message":"sagsdgasdgsdg"
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/removeFinished.json b/services/logs2notifications/internal/handler/testdata/removeFinished.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
new file mode 100644
index 0000000000..287e649a98
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
@@ -0,0 +1 @@
+lagoon-type-override pushed in owner/repository
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
new file mode 100644
index 0000000000..93333fb083
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] lagoon-type-override pushed in owner/repository
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
new file mode 100644
index 0000000000..d68f558315
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
new file mode 100644
index 0000000000..d68f558315
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
new file mode 100644
index 0000000000..efc27fed55
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
@@ -0,0 +1 @@
+[lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
new file mode 100644
index 0000000000..d8bbb51bc0
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
@@ -0,0 +1 @@
+lagoon-type-override pushed in owner/repository deployment skipped
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
new file mode 100644
index 0000000000..beaf598e56
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] lagoon-type-override pushed in owner/repository *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
new file mode 100644
index 0000000000..9dc759260a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
new file mode 100644
index 0000000000..9dc759260a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
new file mode 100644
index 0000000000..ee43a45568
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
@@ -0,0 +1 @@
+[lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/webhook_events.go b/services/logs2notifications/internal/handler/webhook_events.go
new file mode 100644
index 0000000000..6e25acf3d1
--- /dev/null
+++ b/services/logs2notifications/internal/handler/webhook_events.go
@@ -0,0 +1,150 @@
+package handler
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// WebhookData .
+type WebhookData struct {
+ Type string `json:"type"`
+ Event string `json:"event"`
+ Project string `json:"project"`
+ Environment string `json:"environment"`
+}
+
+// SendToWebhook .
+func (h *Messaging) SendToWebhook(notification *Notification, webhook schema.NotificationWebhook) {
+ message, err := h.processWebhookTemplate(notification)
+ if err != nil {
+ return
+ }
+ h.sendWebhookMessage(*message, webhook)
+}
+
+func (h *Messaging) sendWebhookMessage(data WebhookData, webhook schema.NotificationWebhook) {
+ message, _ := json.Marshal(data)
+ req, err := http.NewRequest("POST", webhook.Webhook, bytes.NewBuffer(message))
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Content-Length", fmt.Sprintf("%d", len(message)))
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error sending message to webhook: %v", err)
+ return
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to webhook", data.Event))
+}
+
+// processWebhookTemplate .
+func (h *Messaging) processWebhookTemplate(notification *Notification) (*WebhookData, error) {
+ tpl, err := getWebhookEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return nil, fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+ data := WebhookData{
+ Event: notification.Event,
+ Project: notification.Meta.ProjectName,
+ Environment: notification.Meta.BranchName,
+ }
+
+ switch tpl {
+ case "deployFinished":
+ data.Type = "DEPLOYMENT"
+ case "deployError":
+ data.Type = "DEPLOYMENT"
+ default:
+ return nil, fmt.Errorf("no matching event")
+ }
+ return &data, nil
+}
+
+func getWebhookEvent(msgEvent string) (string, error) {
+ if val, ok := webhookEventTypeMap[msgEvent]; ok {
+ return val.Template, nil
+ }
+ return "", fmt.Errorf("no matching event source")
+}
+
+var webhookEventTypeMap = map[string]EventMap{
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+
+ // deprecated
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
+
+ // deprecated
+ // "task:deploy-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+
+ // deprecated
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+}
diff --git a/services/logs2notifications/internal/helpers/helpers.go b/services/logs2notifications/internal/helpers/helpers.go
new file mode 100644
index 0000000000..46d554bf6f
--- /dev/null
+++ b/services/logs2notifications/internal/helpers/helpers.go
@@ -0,0 +1,77 @@
+package helpers
+
+import (
+ "crypto/sha1"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+const (
+ // DefaultNamespacePattern is what is used when one is not provided.
+ DefaultNamespacePattern = "${project}-${environment}"
+)
+
+// GenerateNamespaceName handles the generation of the namespace name from environment and project name with prefixes and patterns
+func GenerateNamespaceName(pattern, environmentName, projectname, prefix, controllerNamespace string, randomPrefix bool) string {
+ nsPattern := pattern
+ if pattern == "" {
+ nsPattern = DefaultNamespacePattern
+ }
+ environmentName = ShortenEnvironment(projectname, MakeSafe(environmentName))
+ // lowercase and dnsify the namespace against the namespace pattern
+ ns := MakeSafe(
+ strings.Replace(
+ strings.Replace(
+ nsPattern,
+ "${environment}",
+ environmentName,
+ -1,
+ ),
+ "${project}",
+ projectname,
+ -1,
+ ),
+ )
+ // If there is a namespaceprefix defined, and random prefix is disabled
+ // then add the prefix to the namespace
+ if prefix != "" && randomPrefix == false {
+ ns = fmt.Sprintf("%s-%s", prefix, ns)
+ }
+ // If the randomprefix is enabled, then add a prefix based on the hash of the controller namespace
+ if randomPrefix {
+ ns = fmt.Sprintf("%s-%s", HashString(controllerNamespace)[0:8], ns)
+ }
+ // Once the namespace is fully calculated, then truncate the generated namespace
+ // to 63 characters to not exceed the kubernetes namespace limit
+ if len(ns) > 63 {
+ ns = fmt.Sprintf("%s-%s", ns[0:58], HashString(ns)[0:4])
+ }
+ return ns
+}
+
+// ShortenEnvironment shortens the environment name down the same way that Lagoon does
+func ShortenEnvironment(project, environment string) string {
+ overlength := 58 - len(project)
+ if len(environment) > overlength {
+ environment = fmt.Sprintf("%s-%s", environment[0:overlength-5], HashString(environment)[0:4])
+ }
+ return environment
+}
+
+// MakeSafe ensures that any string is dns safe
+func MakeSafe(in string) string {
+ out := regexp.MustCompile(`[^0-9a-z-]`).ReplaceAllString(
+ strings.ToLower(in),
+ "$1-$2",
+ )
+ return out
+}
+
+// HashString get the hash of a given string.
+func HashString(s string) string {
+ h := sha1.New()
+ h.Write([]byte(s))
+ bs := h.Sum(nil)
+ return fmt.Sprintf("%x", bs)
+}
diff --git a/services/logs2notifications/internal/helpers/helpers_test.go b/services/logs2notifications/internal/helpers/helpers_test.go
new file mode 100644
index 0000000000..6967bf4d8d
--- /dev/null
+++ b/services/logs2notifications/internal/helpers/helpers_test.go
@@ -0,0 +1,160 @@
+package helpers
+
+import (
+ "testing"
+)
+
+func TestGenerateNamespaceName(t *testing.T) {
+ type args struct {
+ pattern string
+ environmentName string
+ projectname string
+ prefix string
+ controllerNamespace string
+ randomPrefix bool
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "really long environment name with slash and capitals",
+ args: args{
+ pattern: "",
+ environmentName: "Feature/Really-Exceedingly-Long-Environment-Name-For-A-Branch",
+ projectname: "this-is-my-project",
+ prefix: "",
+ controllerNamespace: "lagoon",
+ randomPrefix: false,
+ },
+ want: "this-is-my-project-feature-really-exceedingly-long-env-dc8c",
+ },
+ {
+ name: "really long environment name with slash and no capitals",
+ args: args{
+ pattern: "",
+ environmentName: "feature/really-exceedingly-long-environment-name-for-a-branch",
+ projectname: "this-is-my-project",
+ prefix: "",
+ controllerNamespace: "lagoon",
+ randomPrefix: false,
+ },
+ want: "this-is-my-project-feature-really-exceedingly-long-env-dc8c",
+ },
+ {
+ name: "short environment name with slash and capitals",
+ args: args{
+ pattern: "",
+ environmentName: "Feature/Branch",
+ projectname: "this-is-my-project",
+ prefix: "",
+ controllerNamespace: "lagoon",
+ randomPrefix: false,
+ },
+ want: "this-is-my-project-feature-branch",
+ },
+ {
+ name: "short environment name with slash and no capitals",
+ args: args{
+ pattern: "",
+ environmentName: "feature/branch",
+ projectname: "this-is-my-project",
+ prefix: "",
+ controllerNamespace: "lagoon",
+ randomPrefix: false,
+ },
+ want: "this-is-my-project-feature-branch",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := GenerateNamespaceName(tt.args.pattern, tt.args.environmentName, tt.args.projectname, tt.args.prefix, tt.args.controllerNamespace, tt.args.randomPrefix); got != tt.want {
+ t.Errorf("GenerateNamespaceName() got %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestMakeSafe(t *testing.T) {
+ tests := []struct {
+ name string
+ in string
+ want string
+ }{
+ {
+ name: "slash in name",
+ in: "Feature/Branch",
+ want: "feature-branch",
+ },
+ {
+ name: "noslash in name",
+ in: "Feature-Branch",
+ want: "feature-branch",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := MakeSafe(tt.in); got != tt.want {
+ t.Errorf("MakeSafe() go %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestHashString(t *testing.T) {
+ tests := []struct {
+ name string
+ in string
+ want string
+ }{
+ {
+ name: "generate hash",
+ in: "feature-branch",
+ want: "011122006d017c21d1376add9f7f65b43555a455",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := HashString(tt.in); got != tt.want {
+ t.Errorf("HashString() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestShortenEnvironment(t *testing.T) {
+ type args struct {
+ project string
+ environment string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "really long environment name with slash and capitals",
+ args: args{
+ environment: MakeSafe("Feature/Really-Exceedingly-Long-Environment-Name-For-A-Branch"),
+ project: "this-is-my-project",
+ },
+ want: "feature-really-exceedingly-long-env-dc8c",
+ },
+ {
+ name: "short environment name",
+ args: args{
+ environment: MakeSafe("Feature/Branch"),
+ project: "this-is-my-project",
+ },
+ want: "feature-branch",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := ShortenEnvironment(tt.args.project, tt.args.environment); got != tt.want {
+ t.Errorf("ShortenEnvironment() got %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql b/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
new file mode 100644
index 0000000000..a1f4eca381
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/_lgraphql/projectNotifications.graphql
@@ -0,0 +1,41 @@
+query (
+ $name: String!
+) {
+ projectByName(
+ name: $name
+ ) {
+ id
+ name
+ notifications {
+ ... on NotificationSlack {
+ __typename
+ webhook
+ name
+ channel
+ }
+ ... on NotificationRocketChat {
+ __typename
+ webhook
+ name
+ channel
+ }
+ ... on NotificationEmail {
+ __typename
+ emailAddress
+ name
+ }
+ ... on NotificationMicrosoftTeams {
+ __typename
+ webhook
+ name
+ }
+ ... on NotificationWebhook {
+ __typename
+ webhook
+ name
+ contentType
+ notificationSeverityThreshold
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/client.go b/services/logs2notifications/internal/lagoon/client/client.go
new file mode 100644
index 0000000000..9ca3bb2624
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/client.go
@@ -0,0 +1,93 @@
+//go:generate go-bindata -pkg lgraphql -o lgraphql/lgraphql.go -nometadata _lgraphql/
+
+// Package client implements the interfaces required by the parent lagoon
+// package.
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+
+ "github.com/machinebox/graphql"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client/lgraphql"
+)
+
+// Client implements the lagoon package interfaces for the Lagoon GraphQL API.
+type Client struct {
+ userAgent string
+ token string
+ client *graphql.Client
+}
+
+// New creates a new Client for the given endpoint.
+func New(endpoint, token, userAgent string, debug bool) *Client {
+ if debug {
+ return &Client{
+ userAgent: userAgent,
+ token: token,
+ client: graphql.NewClient(endpoint,
+ // enable debug logging to stderr
+ func(c *graphql.Client) {
+ l := log.New(os.Stderr, "graphql", 0)
+ c.Log = func(s string) {
+ l.Println(s)
+ }
+ }),
+ }
+ }
+ return &Client{
+ userAgent: userAgent,
+ token: token,
+ client: graphql.NewClient(endpoint),
+ }
+}
+
+// newRequest constructs a graphql request.
+// assetName is the name of the graphql query template in _graphql/.
+// varStruct is converted to a map of variables for the template.
+func (c *Client) newRequest(
+ assetName string, varStruct interface{}) (*graphql.Request, error) {
+
+ q, err := lgraphql.Asset(assetName)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't get asset: %w", err)
+ }
+
+ return c.doRequest(string(q), varStruct)
+}
+
+func (c *Client) doRequest(query string, varStruct interface{}) (*graphql.Request, error) {
+ vars, err := structToVarMap(varStruct)
+ if err != nil {
+ return nil, fmt.Errorf("couldn't convert struct to map: %w", err)
+ }
+
+ req := graphql.NewRequest(query)
+ for key, value := range vars {
+ req.Var(key, value)
+ }
+
+ headers := map[string]string{
+ "User-Agent": c.userAgent,
+ "Authorization": fmt.Sprintf("Bearer %s", c.token),
+ }
+ for key, value := range headers {
+ req.Header.Set(key, value)
+ }
+
+ return req, nil
+}
+
+// structToVarMap encodes the given struct to a map. The idea is that by
+// round-tripping through Marshal/Unmarshal, omitempty is applied to the
+// zero-valued fields.
+func structToVarMap(
+ varStruct interface{}) (vars map[string]interface{}, err error) {
+ data, err := json.Marshal(varStruct)
+ if err != nil {
+ return vars, err
+ }
+ return vars, json.Unmarshal(data, &vars)
+}
diff --git a/services/logs2notifications/internal/lagoon/client/client_test.go b/services/logs2notifications/internal/lagoon/client/client_test.go
new file mode 100644
index 0000000000..c0e17ec55b
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/client_test.go
@@ -0,0 +1,81 @@
+package client_test
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
+)
+
+type testStruct0 struct {
+ Foo string `json:"foo"`
+ Bar uint `json:"bar"`
+ Baz string `json:"baz,omitempty"`
+ Quux uint `json:"quux,omitempty"`
+}
+
+func TestStructToVarMap(t *testing.T) {
+ var testCases = map[string]struct {
+ input testStruct0
+ expect map[string]interface{}
+ }{
+ "simple struct": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 8,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(8),
+ },
+ },
+ "keep zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ },
+ },
+ "omit zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ Baz: "",
+ Quux: 0,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ },
+ },
+ "keep non-zero values": {
+ input: testStruct0{
+ Foo: "abc",
+ Bar: 0,
+ Baz: "hi",
+ Quux: 9,
+ },
+ expect: map[string]interface{}{
+ "foo": "abc",
+ "bar": float64(0),
+ "baz": "hi",
+ "quux": float64(9),
+ },
+ },
+ }
+ for name, tc := range testCases {
+ t.Run(name, func(tt *testing.T) {
+ vars, err := client.StructToVarMap(&tc.input)
+ if err != nil {
+ tt.Error(err)
+ }
+ if !reflect.DeepEqual(vars, tc.expect) {
+ tt.Logf("result:\n%s\nexpected:\n%s", vars, tc.expect)
+ tt.Errorf("result does not match expected")
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/helper_test.go b/services/logs2notifications/internal/lagoon/client/helper_test.go
new file mode 100644
index 0000000000..6561a9155d
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/helper_test.go
@@ -0,0 +1,6 @@
+package client
+
+// StructToVarMap exposes the private client.structToVarMap for tests.
+func StructToVarMap(varStruct interface{}) (map[string]interface{}, error) {
+ return structToVarMap(varStruct)
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go b/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
new file mode 100644
index 0000000000..3e7a131df5
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/lgraphql/lgraphql.go
@@ -0,0 +1,247 @@
+// Code generated by go-bindata. (@generated) DO NOT EDIT.
+
+ //Package lgraphql generated by go-bindata.// sources:
+// _lgraphql/projectNotifications.graphql
+package lgraphql
+
+import (
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+// Name return file name
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+
+// Size return file size
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+
+// Mode return file mode
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+
+// ModTime return file modify time
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+
+// IsDir return file whether a directory
+func (fi bindataFileInfo) IsDir() bool {
+ return fi.mode&os.ModeDir != 0
+}
+
+// Sys return file is sys mode
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var __lgraphqlProjectnotificationsGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x92\xc1\x4a\xc4\x30\x10\x86\xcf\xf6\x29\x46\xf0\xb0\x5e\xfa\x00\xde\x54\x3c\xba\x07\x5b\xf0\xb8\xc4\x74\xd6\x8c\x6d\x67\x6a\x32\x2a\x41\xfa\xee\x92\x96\xc5\xb6\x2a\x45\x50\xd8\xff\x94\xfc\xf9\x66\xf8\x93\xc9\xf3\x0b\xfa\x08\x9b\x0c\xe0\x8c\x4d\x8b\x17\x50\xa8\x27\x7e\x3c\xcd\xce\xe1\x3d\x03\x00\xe8\xbc\x3c\xa1\xd5\xab\xb8\x35\x2d\x6e\x06\x2b\x69\x84\x87\x9a\xc1\x3b\xe0\x49\x54\xcd\xb0\xcf\x8d\x28\xed\xc9\x1a\x25\xe1\x30\xe1\x93\xf2\x3c\x07\x61\xd8\x4e\x90\xa2\x31\xb6\x5e\x60\x49\xbb\x9d\xc6\x0e\x67\x9d\x0f\x7a\xc3\x07\x27\x52\x7f\xf1\xbf\x85\xad\x33\xcc\xd8\xcc\xfc\x7e\x2d\xd4\x9d\xd8\x1a\xf5\xda\x19\x3d\xb6\x64\x37\xad\xa1\xe6\x77\xa1\x30\x95\x5c\x56\x95\xc7\x10\xd6\x93\xad\x26\xb8\x25\xeb\x25\xc8\x5e\x4b\x34\xed\x72\xc0\x7f\xf5\x3e\xab\x29\xee\xc7\x4e\xff\x39\x1e\x61\x45\xd6\x32\x76\xcb\xb3\x93\xe9\x17\x2f\xf0\x15\x3d\x69\x2c\x9d\xc7\xe0\xa4\xa9\x7e\xb8\xc7\xb8\xea\xb3\xfe\x23\x00\x00\xff\xff\x7f\x5a\x0f\xed\x8c\x03\x00\x00")
+
+func _lgraphqlProjectnotificationsGraphqlBytes() ([]byte, error) {
+ return bindataRead(
+ __lgraphqlProjectnotificationsGraphql,
+ "_lgraphql/projectNotifications.graphql",
+ )
+}
+
+func _lgraphqlProjectnotificationsGraphql() (*asset, error) {
+ bytes, err := _lgraphqlProjectnotificationsGraphqlBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "_lgraphql/projectNotifications.graphql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if err != nil {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "_lgraphql/projectNotifications.graphql": _lgraphqlProjectnotificationsGraphql,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+
+var _bintree = &bintree{nil, map[string]*bintree{
+ "_lgraphql": &bintree{nil, map[string]*bintree{
+ "projectNotifications.graphql": &bintree{_lgraphqlProjectnotificationsGraphql, map[string]*bintree{}},
+ }},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
diff --git a/services/logs2notifications/internal/lagoon/client/query.go b/services/logs2notifications/internal/lagoon/client/query.go
new file mode 100644
index 0000000000..a1e9a2cdca
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/client/query.go
@@ -0,0 +1,25 @@
+package client
+
+import (
+ "context"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// NotificationsForProjectByName gets all notifications for a project
+func (c *Client) NotificationsForProjectByName(
+ ctx context.Context, name string, project *schema.Project) error {
+ req, err := c.newRequest("_lgraphql/projectNotifications.graphql",
+ map[string]interface{}{
+ "name": name,
+ })
+ if err != nil {
+ return err
+ }
+
+ return c.client.Run(ctx, req, &struct {
+ Response *schema.Project `json:"projectByName"`
+ }{
+ Response: project,
+ })
+}
diff --git a/services/logs2notifications/internal/lagoon/jwt/jwt.go b/services/logs2notifications/internal/lagoon/jwt/jwt.go
new file mode 100644
index 0000000000..4284685b76
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/jwt/jwt.go
@@ -0,0 +1,30 @@
+package jwt
+
+import (
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+// LagoonClaims is a set of JWT claims used by Lagoon.
+type LagoonClaims struct {
+ Role string `json:"role"`
+ jwt.StandardClaims
+}
+
+// OneMinuteAdminToken returns a JWT admin token valid for one minute.
+func OneMinuteAdminToken(secret, audience, subject, issuer string) (string, error) {
+ now := time.Now()
+ claims := LagoonClaims{
+ Role: "admin",
+ StandardClaims: jwt.StandardClaims{
+ Audience: audience,
+ ExpiresAt: now.Unix() + 60,
+ IssuedAt: now.Unix(),
+ Subject: subject,
+ Issuer: issuer,
+ },
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString([]byte(secret))
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/lagoon/project.go b/services/logs2notifications/internal/lagoon/project.go
new file mode 100644
index 0000000000..7208fc3501
--- /dev/null
+++ b/services/logs2notifications/internal/lagoon/project.go
@@ -0,0 +1,20 @@
+// Package lagoon implements high-level functions for interacting with the
+// Lagoon API.
+package lagoon
+
+import (
+ "context"
+
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
+)
+
+// Project interface contains methods for projects in lagoon.
+type Project interface {
+ NotificationsForProjectByName(ctx context.Context, name string, result *schema.Project) error
+}
+
+// NotificationsForProject gets notifications for a project.
+func NotificationsForProject(ctx context.Context, name string, m Project) (*schema.Project, error) {
+ result := schema.Project{}
+ return &result, m.NotificationsForProjectByName(ctx, name, &result)
+}
diff --git a/services/logs2notifications/internal/schema/notifications.go b/services/logs2notifications/internal/schema/notifications.go
new file mode 100644
index 0000000000..33aeff6f61
--- /dev/null
+++ b/services/logs2notifications/internal/schema/notifications.go
@@ -0,0 +1,107 @@
+package schema
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// Notifications represents possible Lagoon notification types.
+// These are unmarshalled from a projectByName query response.
+type Notifications struct {
+ Slack []NotificationSlack
+ RocketChat []NotificationRocketChat
+ Email []NotificationEmail
+ MicrosoftTeams []NotificationMicrosoftTeams
+ Webhook []NotificationWebhook
+}
+
+// NotificationSlack is based on the Lagoon API type.
+type NotificationSlack struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ Channel string `json:"channel"`
+}
+
+// NotificationRocketChat is based on the Lagoon API type.
+type NotificationRocketChat struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ Channel string `json:"channel"`
+}
+
+// NotificationEmail is based on the Lagoon API type.
+type NotificationEmail struct {
+ Name string `json:"name"`
+ EmailAddress string `json:"emailAddress"`
+}
+
+// NotificationMicrosoftTeams is based on the Lagoon API type.
+type NotificationMicrosoftTeams struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+}
+
+// NotificationWebhook is based on the Lagoon API type.
+type NotificationWebhook struct {
+ Name string `json:"name"`
+ Webhook string `json:"webhook"`
+ ContentType string `json:"contentType"`
+ NotificationSeverityThreshold string `json:"notificationSeverityThreshold"`
+}
+
+// UnmarshalJSON unmashals a quoted json string to the Notification values
+// returned from the Lagoon API.
+func (n *Notifications) UnmarshalJSON(b []byte) error {
+ var nArray []map[string]string
+ err := json.Unmarshal(b, &nArray)
+ if err != nil {
+ return err
+ }
+ for _, nMap := range nArray {
+ if len(nMap) == 0 {
+ // Unsupported notification type returns an empty map.
+ // This happens when the lagoon API being targeted is actually a higher
+ // version than configured.
+ continue
+ }
+ switch nMap["__typename"] {
+ case "NotificationSlack":
+ n.Slack = append(n.Slack,
+ NotificationSlack{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ Channel: nMap["channel"],
+ })
+ case "NotificationRocketChat":
+ n.RocketChat = append(n.RocketChat,
+ NotificationRocketChat{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ Channel: nMap["channel"],
+ })
+ case "NotificationEmail":
+ n.Email = append(n.Email,
+ NotificationEmail{
+ Name: nMap["name"],
+ EmailAddress: nMap["emailAddress"],
+ })
+ case "NotificationMicrosoftTeams":
+ n.MicrosoftTeams = append(n.MicrosoftTeams,
+ NotificationMicrosoftTeams{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ })
+ case "NotificationWebhook":
+ n.Webhook = append(n.Webhook,
+ NotificationWebhook{
+ Name: nMap["name"],
+ Webhook: nMap["webhook"],
+ ContentType: nMap["contentType"],
+ NotificationSeverityThreshold: nMap["notificationSeverityThreshold"],
+ })
+ default:
+ return fmt.Errorf("unknown notification type: %v", nMap["__typename"])
+ }
+ }
+ return nil
+}
diff --git a/services/logs2notifications/internal/schema/project.go b/services/logs2notifications/internal/schema/project.go
new file mode 100644
index 0000000000..d806ac94a8
--- /dev/null
+++ b/services/logs2notifications/internal/schema/project.go
@@ -0,0 +1,8 @@
+package schema
+
+// Project is based on the Lagoon API type.
+type Project struct {
+ ID uint `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Notifications *Notifications `json:"notifications,omitempty"`
+}
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
new file mode 100644
index 0000000000..da2e918cb0
--- /dev/null
+++ b/services/logs2notifications/main.go
@@ -0,0 +1,259 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/cheshir/go-mq"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/handler"
+)
+
+var (
+ httpListenPort = os.Getenv("HTTP_LISTEN_PORT")
+ mqUser string
+ mqPass string
+ mqHost string
+ mqPort string
+ mqWorkers int
+ rabbitReconnectRetryInterval int
+ startupConnectionAttempts int
+ startupConnectionInterval int
+ lagoonAPIHost string
+ lagoonAppID string
+ jwtTokenSigningKey string
+ jwtAudience string
+ jwtSubject string
+ jwtIssuer string
+
+ s3FilesAccessKeyID string
+ s3FilesSecretAccessKey string
+ s3FilesBucket string
+ s3FilesRegion string
+ s3FilesOrigin string
+
+ disableSlack bool
+ disableRocketChat bool
+ disableMicrosoftTeams bool
+ disableEmail bool
+ disableWebhooks bool
+ disableS3 bool
+
+ emailSender string
+ emailSenderPassword string
+ emailHost string
+ emailPort string
+ emailInsecureSkipVerify bool
+)
+
+func main() {
+ flag.StringVar(&lagoonAppID, "lagoon-app-id", "logs2notifications",
+ "The appID to use that will be sent with messages.")
+ flag.StringVar(&mqUser, "rabbitmq-username", "guest",
+ "The username of the rabbitmq user.")
+ flag.StringVar(&mqPass, "rabbitmq-password", "guest",
+ "The password for the rabbitmq user.")
+ flag.StringVar(&mqHost, "rabbitmq-hostname", "localhost",
+ "The hostname for the rabbitmq host.")
+ flag.StringVar(&mqPort, "rabbitmq-port", "5672",
+ "The port for the rabbitmq host.")
+ flag.IntVar(&mqWorkers, "rabbitmq-queue-workers", 1,
+ "The number of workers to start with.")
+ flag.IntVar(&rabbitReconnectRetryInterval, "rabbitmq-reconnect-retry-interval", 30,
+ "The retry interval for rabbitmq.")
+ flag.IntVar(&startupConnectionAttempts, "startup-connection-attempts", 10,
+ "The number of startup attempts before exiting.")
+ flag.IntVar(&startupConnectionInterval, "startup-connection-interval-seconds", 30,
+ "The duration between startup attempts.")
+ flag.StringVar(&lagoonAPIHost, "lagoon-api-host", "http://localhost:3000/graphql",
+ "The host for the lagoon api.")
+ flag.StringVar(&jwtTokenSigningKey, "jwt-token-signing-key", "super-secret-string",
+ "The jwt signing token key or secret.")
+ flag.StringVar(&jwtAudience, "jwt-audience", "api.dev",
+ "The jwt audience.")
+ flag.StringVar(&jwtSubject, "jwt-subject", "logs2notifications",
+ "The jwt audience.")
+ flag.StringVar(&jwtIssuer, "jwt-issuer", "logs2notifications",
+ "The jwt audience.")
+
+ // Other notifications configuration
+ flag.BoolVar(&disableSlack, "disable-slack", false,
+ "Disable the logs2slack feature.")
+ flag.BoolVar(&disableRocketChat, "disable-rocketchat", false,
+ "Disable the logs2rocketchat feature.")
+ flag.BoolVar(&disableMicrosoftTeams, "disable-microsoft-teams", false,
+ "Disable the logs2microsoftteams feature.")
+ flag.BoolVar(&disableWebhooks, "disable-webhooks", false,
+ "Disable the logs2webhooks feature.")
+
+ // S3 configuration
+ flag.BoolVar(&disableS3, "disable-s3", false,
+ "Disable the logs2s3 feature.")
+ flag.StringVar(&s3FilesAccessKeyID, "s3-files-access-key", "minio",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesSecretAccessKey, "s3-files-secret-access-key", "minio123",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesBucket, "s3-files-bucket", "lagoon-files",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesRegion, "s3-files-region", "auto",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesOrigin, "s3-files-origin", "http://minio.127.0.0.1.nip.io:9000",
+ "The jwt audience.")
+
+ // Email sending configuration
+ flag.BoolVar(&disableEmail, "disable-email", false,
+ "Disable the logs2email feature.")
+ flag.StringVar(&emailSender, "email-sender-address", "notifications@lagoon.sh",
+ "The email address to send notifications as.")
+ flag.StringVar(&emailSenderPassword, "email-sender-password", "",
+ "The password (if required) for the sending email address.")
+ flag.StringVar(&emailHost, "email-host", "localhost",
+ "The host name or address for the email server.")
+ flag.StringVar(&emailPort, "email-port", "1025",
+ "The port for the email server.")
+ flag.BoolVar(&emailInsecureSkipVerify, "email-tls-insecure-skip-verify", true,
+ "Use TLS verification when talking to the email server.")
+ flag.Parse()
+
+ // get overrides from environment variables
+ mqUser = getEnv("RABBITMQ_USERNAME", mqUser)
+ mqPass = getEnv("RABBITMQ_PASSWORD", mqPass)
+ mqHost = getEnv("RABBITMQ_ADDRESS", mqHost)
+ mqPort = getEnv("RABBITMQ_PORT", mqPort)
+ lagoonAPIHost = getEnv("GRAPHQL_ENDPOINT", lagoonAPIHost)
+ jwtTokenSigningKey = getEnv("JWT_SECRET", jwtTokenSigningKey)
+ jwtAudience = getEnv("JWT_AUDIENCE", jwtAudience)
+ jwtSubject = getEnv("JWT_SUBJECT", jwtSubject)
+ jwtIssuer = getEnv("JWT_ISSUER", jwtIssuer)
+
+ s3FilesAccessKeyID = getEnv("S3_FILES_ACCESS_KEY_ID", s3FilesAccessKeyID)
+ s3FilesSecretAccessKey = getEnv("S3_FILES_SECRET_ACCESS_KEY", s3FilesSecretAccessKey)
+ s3FilesBucket = getEnv("S3_FILES_BUCKET", s3FilesBucket)
+ s3FilesRegion = getEnv("S3_FILES_REGION", s3FilesRegion)
+ s3FilesOrigin = getEnv("S3_FILES_HOST", s3FilesOrigin)
+
+ emailSender = getEnv("EMAIL_SENDER_ADDRESS", emailSender)
+ emailSenderPassword = getEnv("EMAIL_SENDER_PASSWORD", emailSenderPassword)
+ emailHost = getEnv("EMAIL_HOST", emailHost)
+ emailPort = getEnv("EMAIL_PORT", emailPort)
+
+ enableDebug := true
+
+ // configure the backup handler settings
+ broker := handler.RabbitBroker{
+ Hostname: fmt.Sprintf("%s:%s", mqHost, mqPort),
+ Username: mqUser,
+ Password: mqPass,
+ }
+ graphQLConfig := handler.LagoonAPI{
+ Endpoint: lagoonAPIHost,
+ TokenSigningKey: jwtTokenSigningKey,
+ JWTAudience: jwtAudience,
+ JWTSubject: jwtSubject,
+ JWTIssuer: jwtIssuer,
+ }
+
+ log.Println("logs2notifications running")
+
+ config := mq.Config{
+ ReconnectDelay: time.Duration(rabbitReconnectRetryInterval) * time.Second,
+ Exchanges: mq.Exchanges{
+ {
+ Name: "lagoon-logs",
+ Type: "direct",
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Consumers: mq.Consumers{
+ {
+ Name: "notifications-queue",
+ Queue: "lagoon-logs:notifications",
+ Workers: mqWorkers,
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Queues: mq.Queues{
+ {
+ Name: "lagoon-logs:notifications",
+ Exchange: "lagoon-logs",
+ Options: mq.Options{
+ "durable": true,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ Producers: mq.Producers{
+ {
+ Name: "lagoon-logs",
+ Exchange: "lagoon-logs",
+ Options: mq.Options{
+ "app_id": lagoonAppID,
+ "delivery_mode": "2",
+ "headers": "",
+ "content_type": "",
+ },
+ },
+ },
+ DSN: fmt.Sprintf("amqp://%s:%s@%s/", broker.Username, broker.Password, broker.Hostname),
+ }
+
+ messaging := handler.NewMessaging(config,
+ graphQLConfig,
+ startupConnectionAttempts,
+ startupConnectionInterval,
+ enableDebug,
+ lagoonAppID,
+ disableSlack,
+ disableRocketChat,
+ disableMicrosoftTeams,
+ disableEmail,
+ disableWebhooks,
+ disableS3,
+ emailSender,
+ emailSenderPassword,
+ emailHost,
+ emailPort,
+ emailInsecureSkipVerify,
+ s3FilesAccessKeyID,
+ s3FilesSecretAccessKey,
+ s3FilesBucket,
+ s3FilesRegion,
+ s3FilesOrigin,
+ )
+
+ // start the consumer
+ messaging.Consumer()
+
+}
+
+func getEnv(key, fallback string) string {
+ if value, ok := os.LookupEnv(key); ok {
+ return value
+ }
+ return fallback
+}
+
+// accepts fallback values 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False
+// anything else is false.
+func getEnvBool(key string, fallback bool) bool {
+ if value, ok := os.LookupEnv(key); ok {
+ rVal, _ := strconv.ParseBool(value)
+ return rVal
+ }
+ return fallback
+}
From aab9261895bae3d08ce9181654bfc0e3476b6f5f Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 09:49:34 +1100
Subject: [PATCH 04/17] chore: disable logs2other services
---
Makefile | 19 +++++++------------
docker-compose.yaml | 40 ++--------------------------------------
2 files changed, 9 insertions(+), 50 deletions(-)
diff --git a/Makefile b/Makefile
index a28502930e..68c66c5274 100644
--- a/Makefile
+++ b/Makefile
@@ -193,12 +193,7 @@ services := api \
logs-concentrator \
logs-dispatcher \
logs-tee \
- logs2email \
- logs2microsoftteams \
- logs2rocketchat \
- logs2slack \
- logs2s3 \
- logs2webhook \
+ logs2notifications \
storage-calculator \
ui \
webhook-handler \
@@ -218,7 +213,7 @@ $(build-services):
touch $@
# Dependencies of Service Images
-build/auth-server build/logs2email build/logs2slack build/logs2rocketchat build/logs2s3 build/logs2webhook build/logs2microsoftteams build/backup-handler build/controllerhandler build/webhook-handler build/webhooks2tasks build/api build/ui: build/yarn-workspace-builder
+build/auth-server build/logs2notifications build/backup-handler build/controllerhandler build/webhook-handler build/webhooks2tasks build/api build/ui: build/yarn-workspace-builder
build/api-db: services/api-db/Dockerfile
build/api-redis: services/api-redis/Dockerfile
build/actions-handler: services/actions-handler/Dockerfile
@@ -298,7 +293,7 @@ wait-for-keycloak:
grep -m 1 "Config of Keycloak done." <(docker-compose -p $(CI_BUILD_TAG) --compatibility logs -f keycloak 2>&1)
# Define a list of which Lagoon Services are needed for running any deployment testing
-main-test-services = actions-handler broker logs2email logs2slack logs2rocketchat logs2microsoftteams logs2s3 logs2webhook api api-db api-redis keycloak keycloak-db ssh auth-server local-git local-api-data-watcher-pusher local-minio
+main-test-services = actions-handler broker logs2notifications api api-db api-redis keycloak keycloak-db ssh auth-server local-git local-api-data-watcher-pusher local-minio
# List of Lagoon Services needed for webhook endpoint testing
webhooks-test-services = webhook-handler webhooks2tasks backup-handler
@@ -502,8 +497,8 @@ api-development: build/api build/api-db build/local-api-data-watcher-pusher buil
IMAGE_REPO=$(CI_BUILD_TAG) docker-compose -p $(CI_BUILD_TAG) --compatibility up -d api api-db local-api-data-watcher-pusher keycloak keycloak-db broker api-redis
.PHONY: ui-logs-development
-ui-logs-development: build/actions-handler build/api build/api-db build/local-api-data-watcher-pusher build/ui build/keycloak build/keycloak-db build/broker-single build/api-redis build/logs2s3 build/local-minio
- IMAGE_REPO=$(CI_BUILD_TAG) docker-compose -p $(CI_BUILD_TAG) --compatibility up -d api api-db actions-handler local-api-data-watcher-pusher ui keycloak keycloak-db broker api-redis logs2s3 local-minio
+ui-logs-development: build/actions-handler build/api build/api-db build/local-api-data-watcher-pusher build/ui build/keycloak build/keycloak-db build/broker-single build/api-redis build/logs2notifications build/local-minio
+ IMAGE_REPO=$(CI_BUILD_TAG) docker-compose -p $(CI_BUILD_TAG) --compatibility up -d api api-db actions-handler local-api-data-watcher-pusher ui keycloak keycloak-db broker api-redis logs2notifications local-minio
## CI targets
@@ -634,7 +629,7 @@ ifeq ($(ARCH), darwin)
tcp-listen:32080,fork,reuseaddr tcp-connect:target:32080
endif
-KIND_SERVICES = api api-db api-redis auth-server actions-handler broker controllerhandler docker-host drush-alias keycloak keycloak-db logs2s3 webhook-handler webhooks2tasks kubectl-build-deploy-dind local-api-data-watcher-pusher local-git ssh tests ui workflows
+KIND_SERVICES = api api-db api-redis auth-server actions-handler broker controllerhandler docker-host drush-alias keycloak keycloak-db logs2notifications logs2s3 webhook-handler webhooks2tasks kubectl-build-deploy-dind local-api-data-watcher-pusher local-git ssh tests ui workflows
KIND_TESTS = local-api-data-watcher-pusher local-git tests
KIND_TOOLS = kind helm kubectl jq stern
@@ -667,7 +662,7 @@ kind/test: kind/cluster helm/repos $(addprefix local-dev/,$(KIND_TOOLS)) $(addpr
"quay.io/helmpack/chart-testing:$(CHART_TESTING_VERSION)" \
ct install
-LOCAL_DEV_SERVICES = api auth-server controllerhandler logs2email logs2microsoftteams logs2rocketchat logs2slack logs2s3 logs2webhook ui webhook-handler webhooks2tasks
+LOCAL_DEV_SERVICES = api auth-server controllerhandler logs2notifications ui webhook-handler webhooks2tasks
# install lagoon charts in a Kind cluster
.PHONY: kind/setup
diff --git a/docker-compose.yaml b/docker-compose.yaml
index e63293174e..ea110b3551 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -33,44 +33,8 @@ services:
volumes:
- ./services/controllerhandler/src:/app/services/controllerhandler/src
- ./node-packages:/app/node-packages:delegated
- logs2rocketchat:
- image: ${IMAGE_REPO:-lagoon}/logs2rocketchat
- command: yarn run dev
- volumes:
- - ./services/logs2rocketchat/src:/app/services/logs2rocketchat/src
- - ./node-packages:/app/node-packages:delegated
- logs2slack:
- image: ${IMAGE_REPO:-lagoon}/logs2slack
- command: yarn run dev
- volumes:
- - ./services/logs2slack/src:/app/services/logs2slack/src
- - ./node-packages:/app/node-packages:delegated
- logs2webhook:
- image: ${IMAGE_REPO:-lagoon}/logs2webhook
- command: yarn run dev
- volumes:
- - ./services/logs2webhook/src:/app/services/logs2webhook/src
- - ./node-packages:/app/node-packages:delegated
- logs2s3:
- image: ${IMAGE_REPO:-lagoon}/logs2s3
- command: yarn run dev
- volumes:
- - ./services/logs2s3/src:/app/services/logs2s3/src
- - ./node-packages:/app/node-packages:delegated
- logs2microsoftteams:
- image: ${IMAGE_REPO:-lagoon}/logs2microsoftteams
- command: yarn run dev
- volumes:
- - ./services/logs2microsoftteams/src:/app/services/logs2microsoftteams/src
- - ./node-packages:/app/node-packages:delegated
- logs2email:
- image: ${IMAGE_REPO:-lagoon}/logs2email
- command: yarn run dev
- volumes:
- - ./services/logs2email/src:/app/services/logs2email/src
- - ./node-packages:/app/node-packages:delegated
- depends_on:
- - mailhog
+ logs2notifications:
+ image: ${IMAGE_REPO:-lagoon}/logs2notifications
mailhog:
image: mailhog/mailhog
ports:
From 9590b70fc7a6ef30f17e934c86274905b4628e05 Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 15:56:39 +1100
Subject: [PATCH 05/17] chore: remove old logs2x services
---
services/logs2email/.babelrc | 8 -
services/logs2email/.gitignore | 2 -
services/logs2email/Dockerfile | 42 --
services/logs2email/README.md | 34 --
services/logs2email/entrypoints/50-ssmtp.sh | 48 ---
services/logs2email/package.json | 29 --
services/logs2email/src/index.ts | 26 --
services/logs2email/src/readFromRabbitMQ.ts | 365 ------------------
services/logs2email/ssmtp.conf | 4 -
services/logs2email/tsconfig.json | 10 -
services/logs2microsoftteams/.babelrc | 8 -
services/logs2microsoftteams/.gitignore | 2 -
services/logs2microsoftteams/Dockerfile | 34 --
services/logs2microsoftteams/README.md | 34 --
services/logs2microsoftteams/package.json | 33 --
services/logs2microsoftteams/src/index.ts | 26 --
.../src/readFromRabbitMQ.ts | 257 ------------
services/logs2microsoftteams/tsconfig.json | 10 -
services/logs2rocketchat/.babelrc | 8 -
services/logs2rocketchat/.gitignore | 2 -
services/logs2rocketchat/Dockerfile | 34 --
services/logs2rocketchat/README.md | 34 --
services/logs2rocketchat/package.json | 33 --
services/logs2rocketchat/src/index.ts | 26 --
.../logs2rocketchat/src/readFromRabbitMQ.ts | 264 -------------
services/logs2rocketchat/tsconfig.json | 10 -
services/logs2s3/.gitignore | 2 -
services/logs2s3/Dockerfile | 34 --
services/logs2s3/README.md | 31 --
services/logs2s3/package.json | 35 --
services/logs2s3/src/index.ts | 26 --
services/logs2s3/src/readFromRabbitMQ.ts | 96 -----
services/logs2s3/tsconfig.json | 10 -
services/logs2slack/.gitignore | 2 -
services/logs2slack/Dockerfile | 34 --
services/logs2slack/README.md | 34 --
services/logs2slack/package.json | 34 --
services/logs2slack/src/index.ts | 26 --
services/logs2slack/src/readFromRabbitMQ.ts | 133 -------
services/logs2slack/tsconfig.json | 10 -
services/logs2webhook/.gitignore | 2 -
services/logs2webhook/Dockerfile | 34 --
services/logs2webhook/README.md | 33 --
services/logs2webhook/package.json | 34 --
services/logs2webhook/src/index.ts | 26 --
services/logs2webhook/src/readFromRabbitMQ.ts | 105 -----
services/logs2webhook/tsconfig.json | 10 -
47 files changed, 2134 deletions(-)
delete mode 100644 services/logs2email/.babelrc
delete mode 100644 services/logs2email/.gitignore
delete mode 100644 services/logs2email/Dockerfile
delete mode 100644 services/logs2email/README.md
delete mode 100755 services/logs2email/entrypoints/50-ssmtp.sh
delete mode 100644 services/logs2email/package.json
delete mode 100644 services/logs2email/src/index.ts
delete mode 100644 services/logs2email/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2email/ssmtp.conf
delete mode 100644 services/logs2email/tsconfig.json
delete mode 100644 services/logs2microsoftteams/.babelrc
delete mode 100644 services/logs2microsoftteams/.gitignore
delete mode 100644 services/logs2microsoftteams/Dockerfile
delete mode 100644 services/logs2microsoftteams/README.md
delete mode 100644 services/logs2microsoftteams/package.json
delete mode 100644 services/logs2microsoftteams/src/index.ts
delete mode 100644 services/logs2microsoftteams/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2microsoftteams/tsconfig.json
delete mode 100644 services/logs2rocketchat/.babelrc
delete mode 100644 services/logs2rocketchat/.gitignore
delete mode 100644 services/logs2rocketchat/Dockerfile
delete mode 100644 services/logs2rocketchat/README.md
delete mode 100644 services/logs2rocketchat/package.json
delete mode 100644 services/logs2rocketchat/src/index.ts
delete mode 100644 services/logs2rocketchat/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2rocketchat/tsconfig.json
delete mode 100644 services/logs2s3/.gitignore
delete mode 100644 services/logs2s3/Dockerfile
delete mode 100644 services/logs2s3/README.md
delete mode 100644 services/logs2s3/package.json
delete mode 100644 services/logs2s3/src/index.ts
delete mode 100644 services/logs2s3/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2s3/tsconfig.json
delete mode 100644 services/logs2slack/.gitignore
delete mode 100644 services/logs2slack/Dockerfile
delete mode 100644 services/logs2slack/README.md
delete mode 100644 services/logs2slack/package.json
delete mode 100644 services/logs2slack/src/index.ts
delete mode 100644 services/logs2slack/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2slack/tsconfig.json
delete mode 100644 services/logs2webhook/.gitignore
delete mode 100644 services/logs2webhook/Dockerfile
delete mode 100644 services/logs2webhook/README.md
delete mode 100644 services/logs2webhook/package.json
delete mode 100644 services/logs2webhook/src/index.ts
delete mode 100644 services/logs2webhook/src/readFromRabbitMQ.ts
delete mode 100644 services/logs2webhook/tsconfig.json
diff --git a/services/logs2email/.babelrc b/services/logs2email/.babelrc
deleted file mode 100644
index 5eba7fc969..0000000000
--- a/services/logs2email/.babelrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "presets": [
- "presets": [["latest-node", { "target": "10" }]]
- ],
- "plugins": [
- "transform-es2015-modules-commonjs"
- ]
-}
diff --git a/services/logs2email/.gitignore b/services/logs2email/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2email/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2email/Dockerfile b/services/logs2email/Dockerfile
deleted file mode 100644
index 44a3ded8bc..0000000000
--- a/services/logs2email/Dockerfile
+++ /dev/null
@@ -1,42 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-COPY entrypoints/50-ssmtp.sh /lagoon/entrypoints/
-COPY ssmtp.conf /etc/ssmtp/ssmtp.conf
-
-RUN apk add --no-cache ssmtp
-
-RUN fix-permissions /etc/ssmtp/ssmtp.conf \
- && fix-permissions /usr/sbin/ssmtp
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2email/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2email/README.md b/services/logs2email/README.md
deleted file mode 100644
index 9e4cced6c2..0000000000
--- a/services/logs2email/README.md
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to Email (`logs2email`)
-
-Watches all the Lagoon logs and checks for events that should trigger an email
-notification. Each log message is tied to a Lagoon project, and email
-configuration for that project is retrieved from the Lagoon API.
-
-Examples of events that might trigger an email: GitHub pull request opened, a new
-build for a Lagoon project environent has started, a task was completed.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:email`
-* Produces: `lagoon-logs:email`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2email/entrypoints/50-ssmtp.sh b/services/logs2email/entrypoints/50-ssmtp.sh
deleted file mode 100755
index c5a51f7ea1..0000000000
--- a/services/logs2email/entrypoints/50-ssmtp.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/sh
-
-
-if [ ${SSMTP_REWRITEDOMAIN+x} ]; then
- echo -e "\nrewriteDomain=${SSMTP_REWRITEDOMAIN}" >> /etc/ssmtp/ssmtp.conf
-fi
-if [ ${SSMTP_AUTHUSER+x} ]; then
- echo -e "\nAuthUser=${SSMTP_AUTHUSER}" >> /etc/ssmtp/ssmtp.conf
-fi
-if [ ${SSMTP_AUTHPASS+x} ]; then
- echo -e "\nAuthPass=${SSMTP_AUTHPASS}" >> /etc/ssmtp/ssmtp.conf
-fi
-if [ ${SSMTP_USETLS+x} ]; then
- echo -e "\nUseTLS=${SSMTP_USETLS}" >> /etc/ssmtp/ssmtp.conf
-fi
-if [ ${SSMTP_USESTARTTLS+x} ]; then
- echo -e "\nUseSTARTTLS=${SSMTP_USESTARTTLS}" >> /etc/ssmtp/ssmtp.conf
-fi
-
-if [ ${SSMTP_MAILHUB+x} ]; then
- echo -e "\nmailhub=${SSMTP_MAILHUB}" >> /etc/ssmtp/ssmtp.conf
-else
- # check if we find a mailhog on 172.17.0.1:1025
- if nc -z -w 1 172.17.0.1 1025 &> /dev/null; then
- echo -e "\nmailhub=172.17.0.1:1025" >> /etc/ssmtp/ssmtp.conf
- return
- fi
- # check if mxout.lagoon.svc can do smtp TLS
- if nc -z -w 1 mxout.lagoon.svc 465 &> /dev/null; then
- echo -e "UseTLS=Yes\nmailhub=mxout.lagoon.svc:465" >> /etc/ssmtp/ssmtp.conf
- return
- fi
- # Fallback: check if mxout.lagoon.svc can do regular 25 smtp
- if nc -z -w 1 mxout.lagoon.svc 25 &> /dev/null; then
- echo -e "\nmailhub=mxout.lagoon.svc:25" >> /etc/ssmtp/ssmtp.conf
- return
- fi
- # check if mxout.default.svc can do smtp TLS
- if nc -z -w 1 mxout.default.svc 465 &> /dev/null; then
- echo -e "UseTLS=Yes\nmailhub=mxout.default.svc:465" >> /etc/ssmtp/ssmtp.conf
- return
- fi
- # Fallback: check if mxout.default.svc can do regular 25 smtp
- if nc -z -w 1 mxout.default.svc 25 &> /dev/null; then
- echo -e "\nmailhub=mxout.default.svc:25" >> /etc/ssmtp/ssmtp.conf
- return
- fi
-fi
diff --git a/services/logs2email/package.json b/services/logs2email/package.json
deleted file mode 100644
index c6abda9651..0000000000
--- a/services/logs2email/package.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "name": "logs2email",
- "version": "0.0.1",
- "description": "lagoon handler for email notifications",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": ["../../node-packages/commons/dist/"],
- "watch": ["src", "../../node-packages/"],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "amqp-connection-manager": "^1.3.5",
- "nodemailer": "^6.3.0"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2email/src/index.ts b/services/logs2email/src/index.ts
deleted file mode 100644
index d85919133a..0000000000
--- a/services/logs2email/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:email', {durable: true}),
- channel.bindQueue('lagoon-logs:email', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:email', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2email/src/readFromRabbitMQ.ts b/services/logs2email/src/readFromRabbitMQ.ts
deleted file mode 100644
index a3dbe3d266..0000000000
--- a/services/logs2email/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,365 +0,0 @@
-import nodemailer from 'nodemailer';
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { getEmailInfoForProject } from '@lagoon/commons/dist/api';
-import { notificationIntToContentType, notificationContentTypeToInt, parseProblemNotification } from '@lagoon/commons/dist/notificationCommons';
-
-let transporter = nodemailer.createTransport({
- sendmail: true,
- newline: 'unix',
- path: '/usr/sbin/ssmtp'
-});
-
-export async function readFromRabbitMQ (msg: ConsumeMessage, channelWrapperLogs: ChannelWrapper): Promise {
- const logMessage = JSON.parse(msg.content.toString())
-
- const {
- severity,
- project,
- uuid,
- event,
- meta,
- message
- } = logMessage
-
- const appId = msg.properties.appId || ""
-
- logger.verbose(`received ${event} for project ${project}`)
-
- var messageMeta = {
- subject: '',
- mainHtml: '',
- plainText: '',
- additional: '',
- color: '#E8E8E8',
- emoji: '❔'
- }
-
- switch (event) {
-
- case "github:pull_request:opened:handled":
- case "gitlab:merge_request:opened:handled":
- case "bitbucket:pullrequest:created:opened:handled":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `PR #${meta.pullrequestNumber} (${meta.pullrequestTitle} opened in ${meta.repoName}`
- messageMeta.plainText = `[${meta.projectName}] PR #${meta.pullrequestNumber} - ${meta.pullrequestTitle} opened in ${meta.repoName}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:synchronize:handled":
- case "bitbucket:pullrequest:updated:opened:handled":
- case "gitlab:merge_request:updated:handled":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `PR #${meta.pullrequestNumber} (${meta.pullrequestTitle}) updated in ${meta.repoName}`
- messageMeta.plainText = `[${meta.projectName}] PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) updated in ${meta.repoName}`
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:pullrequest:fulfilled:handled":
- case "bitbucket:pullrequest:rejected:handled":
- case "github:pull_request:closed:handled":
- case "gitlab:merge_request:closed:handled":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `[${meta.projectName}] PR #${meta.pullrequestNumber} (${meta.pullrequestTitle}) closed in ${meta.repoName}`
- messageMeta.plainText = `[${meta.projectName}] PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) closed in ${meta.repoName}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:deploy":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `REST pullrequest deploy trigger ${meta.pullrequestTitle}
`
- messageMeta.plainText = `[${meta.projectName}] REST pullrequest deploy trigger for PR : ${meta.pullrequestTitle}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "github:delete:handled":
- case "gitlab:remove:handled":
- case "bitbucket:delete:handled":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `Deleted environment ${meta.branchName}
`
- messageMeta.plainText = `[${meta.projectName}] deleted environment ${meta.branchName}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:remove:receive":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `REST remove trigger ${meta.branchName}
`
- messageMeta.plainText = `[${meta.projectName}] REST remove trigger ${meta.branchName}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:repo:push:handled":
- case "github:push:handled":
- case "gitlab:push:handled":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `${meta.branchName}`
- messageMeta.plainText = `[${meta.projectName}] ${meta.branchName}`
- if (meta.shortSha){
- messageMeta.mainHtml = `${messageMeta.plainText} ${meta.shortSha}`
- messageMeta.plainText = `${messageMeta.plainText} (${meta.shortSha})`
- }
- messageMeta.mainHtml = `${messageMeta.mainHtml} pushed in ${meta.repoFullName}`
- messageMeta.plainText = `${messageMeta.plainText} pushed in ${meta.repoFullName}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "gitlab:push:skipped":
- case "github:push:skipped":
- case "bitbucket:push:skipped":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `${meta.branchName}`
- messageMeta.plainText = `[${meta.projectName}] ${meta.branchName}`
- if (meta.shortSha){
- messageMeta.mainHtml = `${messageMeta.plainText} ${meta.shortSha}`
- messageMeta.plainText = `${messageMeta.plainText} (${meta.shortSha})`
- }
- messageMeta.mainHtml = `${messageMeta.plainText} pushed in ${meta.repoFullName} deployment skipped`
- messageMeta.plainText = `${messageMeta.plainText} pushed in ${meta.repoFullName} *deployment skipped*`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "api:deployEnvironmentBranch":
- case "api:deployEnvironmentLatest":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `API deploy trigger ${meta.branchName}
`
- messageMeta.plainText = `[${meta.projectName}] API deploy trigger on branch: ${meta.branchName}`
- if (meta.shortSha) {
- messageMeta.mainHtml = `${messageMeta.mainHtml} (${meta.shortSha})`
- messageMeta.plainText = `${messageMeta.plainText} (${meta.shortSha})`
- }
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:deploy:receive":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '️ℹ️'
- messageMeta.mainHtml = `REST deploy trigger on branch ${meta.branchName}
`
- messageMeta.plainText = `[${meta.projectName}] REST deploy trigger on branch: ${meta.branchName}`
- if (meta.shortSha) {
- messageMeta.mainHtml = `${messageMeta.mainHtml} (${meta.shortSha})`
- messageMeta.plainText = `${messageMeta.plainText} (${meta.shortSha})`
- }
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:promote:receive":
- messageMeta.color = '#E8E8E8'
- messageMeta.emoji = '⚠️'
- messageMeta.mainHtml = `REST promote trigger : Branch ${meta.branchName}
-> ${meta.promoteSourceEnvironment}
`
- messageMeta.plainText = `[${meta.projectName}] REST promote trigger : ${meta.branchName} -> ${meta.promoteSourceEnvironment}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:finished":
- case "task:remove-openshift-resources:finished":
- case "task:builddeploy-openshift:complete":
- messageMeta.color = 'lawngreen'
- messageMeta.emoji = '✅'
- messageMeta.plainText = `[${meta.projectName}] `
- if (meta.shortSha) {
- messageMeta.mainHtml = `${messageMeta.plainText} ${meta.branchName}
(${meta.shortSha})`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName} (${meta.shortSha})`
- } else {
- messageMeta.mainHtml = `${messageMeta.plainText} ${meta.branchName}
`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName}`
- }
- messageMeta.mainHtml = `${messageMeta.plainText} Build ${meta.buildName}
complete.`
- messageMeta.plainText = `${messageMeta.plainText} Build ${meta.buildName} complete.`
- messageMeta.subject = messageMeta.plainText
- if (meta.logLink){
- messageMeta.mainHtml = `${messageMeta.plainText} Logs`
- messageMeta.plainText = `${messageMeta.plainText} [Logs](${meta.logLink})\n`
- }
- messageMeta.plainText = `\n${messageMeta.plainText}${meta.route}\n ${meta.routes.join("\n")}`
- messageMeta.additional = ``
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:remove":
- messageMeta.color = 'lawngreen'
- messageMeta.emoji = '✅'
- messageMeta.mainHtml = `REST pullrequest remove trigger ${meta.pullrequestNumber}`
- messageMeta.plainText = `[${meta.projectName}] REST pullrequest remove trigger ${meta.pullrequestNumber}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "task:remove-openshift:finished":
- case "task:remove-kubernetes:finished":
- messageMeta.color = 'lawngreen'
- messageMeta.emoji = '✅'
- messageMeta.mainHtml = `Remove ${meta.openshiftProject}
`
- messageMeta.plainText = `[${meta.projectName}] remove ${meta.openshiftProject}`
- messageMeta.subject = messageMeta.plainText
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:retry":
- case "task:remove-openshift:retry":
- case "task:remove-kubernetes:retry":
- case "task:remove-openshift-resources:retry":
- messageMeta.color = 'gold'
- messageMeta.emoji = '⚠️'
- messageMeta.plainText = `[${meta.projectName}]`
- if (meta.shortSha) {
- messageMeta.mainHtml = `${meta.branchName}
(${meta.shortSha})`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName} (${meta.shortSha})`
- } else {
- messageMeta.mainHtml = `${messageMeta.mainHtml} ${meta.branchName}
`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName}`
- }
- messageMeta.mainHtml = `${messageMeta.mainHtml} Build ${meta.buildName}
retried.`
- messageMeta.plainText = `${messageMeta.plainText} Build ${meta.buildName} retried.`
- messageMeta.subject = messageMeta.plainText
- if (meta.logLink){
- messageMeta.mainHtml = `${messageMeta.mainHtml} ${meta.logLink}`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.logLink}`
- }
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:error":
- case "task:remove-openshift:error":
- case "task:remove-kubernetes:error":
- case "task:remove-openshift-resources:error":
- case "task:builddeploy-openshift:failed":
- messageMeta.color = 'red'
- messageMeta.emoji = '‼️'
- messageMeta.plainText = `[${meta.projectName}]`
- if (meta.shortSha) {
- messageMeta.mainHtml = `${meta.branchName}
(${meta.shortSha})`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName} (${meta.shortSha})`
- } else {
- messageMeta.mainHtml = `${messageMeta.mainHtml} ${meta.branchName}
`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.branchName}`
- }
- messageMeta.mainHtml = `${messageMeta.mainHtml} Build ${meta.buildName}
error.`
- messageMeta.plainText = `${messageMeta.plainText} Build ${meta.buildName} error.`
- messageMeta.subject = messageMeta.plainText
- if (meta.logLink){
- messageMeta.mainHtml = `${messageMeta.mainHtml} ${meta.logLink}`
- messageMeta.plainText = `${messageMeta.plainText} ${meta.logLink}`
- }
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:closed:CannotDeleteProductionEnvironment":
- case "github:push:CannotDeleteProductionEnvironment":
- case "bitbucket:repo:push:CannotDeleteProductionEnvironment":
- case "gitlab:push:CannotDeleteProductionEnvironment":
- case "rest:remove:CannotDeleteProductionEnvironment":
- messageMeta.color = 'gold'
- messageMeta.emoji = '⚠️'
- messageMeta.mainHtml = `${meta.branchName}
not deleted. ${meta.error}`
- messageMeta.plainText = `[${meta.name}] ${meta.branchName} not deleted.`
- messageMeta.subject = messageMeta.plainText
- messageMeta.plainText = `${messageMeta.plainText} ${meta.error}`
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId)
- break;
-
- default:
- //since there's no single point of acknowlegement of the msg, we need to keep track of whether we've handled the message
- let eventHandledAsProblem = dispatchProblemEventToEmail(event, project, message, messageMeta, channelWrapperLogs, msg, appId);
- if(!eventHandledAsProblem) {
- return channelWrapperLogs.ack(msg);
- }
- }
-}
-
-const dispatchProblemEventToEmail = (event, project, message, messageMeta, channelWrapperLogs, msg, appId) => {
- const problemEvent = parseProblemNotification(event);
- if(problemEvent.isProblem && problemEvent.eventType == 'insert') {
- messageMeta.color = 'gold'
- messageMeta.emoji = '⚠️'
- messageMeta.mainHtml = message
- messageMeta.plainText = message
- messageMeta.subject = `New Problem of severity ${problemEvent.severityLevel} detected on ${project}`
- sendToEmail(project, messageMeta, channelWrapperLogs, msg, appId, 'PROBLEM', problemEvent.severityLevel)
- return true;
- }
- return false;
-};
-
-const sendToEmail = async (project, messageMeta, channelWrapperLogs, msg, appId, contentType = 'DEPLOYMENT', severityLevel = 'NONE') => {
- let projectEmails;
- try {
- projectEmails = await getEmailInfoForProject(project, contentType)
- }
- catch (error) {
- logger.error(`No Email information found, error: ${error}`)
- return channelWrapperLogs.ack(msg)
- }
-
- projectEmails.forEach(projectEmail => {
- const { emailAddress } = projectEmail;
-
- const notificationThresholdMet = notificationContentTypeToInt(projectEmail.notificationSeverityThreshold) <= notificationContentTypeToInt(severityLevel);
- if(contentType == 'PROBLEM' && !notificationThresholdMet) { return; } //go to next iteration
-
- let info = transporter.sendMail({
- from: 'lagoon@amazee.io',
- to: emailAddress,
- subject: messageMeta.subject,
- text: messageMeta.plainText,
- html: `
-
-
-
- Test Email Sample
-
-
-
-
-
-
-
-
${messageMeta.emoji} [${project}]
-
- ${messageMeta.mainHtml}
-
-
-
-
- ${messageMeta.additional}
-
-
-
-`
- });
- });
- channelWrapperLogs.ack(msg)
- return
-}
diff --git a/services/logs2email/ssmtp.conf b/services/logs2email/ssmtp.conf
deleted file mode 100644
index 1659e71b49..0000000000
--- a/services/logs2email/ssmtp.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-# ssmtp config (will be filled during entrypoint 50-ssmtp.sh)
-
-# Email 'From header's can override the default domain
-FromLineOverride=yes
diff --git a/services/logs2email/tsconfig.json b/services/logs2email/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2email/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
diff --git a/services/logs2microsoftteams/.babelrc b/services/logs2microsoftteams/.babelrc
deleted file mode 100644
index f2314e7303..0000000000
--- a/services/logs2microsoftteams/.babelrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "presets": [
- "node8-es6"
- ],
- "plugins": [
- "transform-es2015-modules-commonjs"
- ]
-}
diff --git a/services/logs2microsoftteams/.gitignore b/services/logs2microsoftteams/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2microsoftteams/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2microsoftteams/Dockerfile b/services/logs2microsoftteams/Dockerfile
deleted file mode 100644
index e9cb4af2c3..0000000000
--- a/services/logs2microsoftteams/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2microsoftteams/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2microsoftteams/README.md b/services/logs2microsoftteams/README.md
deleted file mode 100644
index 2bdd0517c4..0000000000
--- a/services/logs2microsoftteams/README.md
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to Microsoft Teams (`logs2microsoftteams`)
-
-Watches all the Lagoon logs and checks for events that should trigger a
-Microsoft Teams message. Each log message is tied to a Lagoon project, and
-channel configuration for that project is retrieved from the Lagoon API.
-
-Examples of events that might trigger a message: GitHub pull request opened, a
-new build for a Lagoon project environment has started, a task was completed.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:microsoftTeams`
-* Produces: `lagoon-logs:microsoftTeams`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2microsoftteams/package.json b/services/logs2microsoftteams/package.json
deleted file mode 100644
index 83fb4db890..0000000000
--- a/services/logs2microsoftteams/package.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "name": "logs2microsoftTeams",
- "version": "0.0.1",
- "description": "lagoon handler for logging message to Microsoft Teams",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": [
- "../../node-packages/commons/dist/"
- ],
- "watch": [
- "src",
- "../../node-packages/"
- ],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "amqp-connection-manager": "^1.3.5"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2microsoftteams/src/index.ts b/services/logs2microsoftteams/src/index.ts
deleted file mode 100644
index b4f6dc52cf..0000000000
--- a/services/logs2microsoftteams/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:microsoftTeams', {durable: true}),
- channel.bindQueue('lagoon-logs:microsoftTeams', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:microsoftTeams', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2microsoftteams/src/readFromRabbitMQ.ts b/services/logs2microsoftteams/src/readFromRabbitMQ.ts
deleted file mode 100644
index f8c328769b..0000000000
--- a/services/logs2microsoftteams/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-import { URL } from 'url';
-import http from 'https';
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { getMicrosoftTeamsInfoForProject } from '@lagoon/commons/dist/api';
-import { notificationIntToContentType, notificationContentTypeToInt, parseProblemNotification } from '@lagoon/commons/dist/notificationCommons';
-
-export async function readFromRabbitMQ (msg: ConsumeMessage, channelWrapperLogs: ChannelWrapper): Promise {
- const logMessage = JSON.parse(msg.content.toString())
-
- const {
- severity,
- project,
- uuid,
- event,
- meta,
- message
- } = logMessage
-
- const appId = msg.properties.appId || ""
-
- logger.verbose(`received ${event} for project ${project}`)
-
- const whiteCheckMark = 'https://statics.teams.microsoft.com/evergreen-assets/emojioneassets/assets-png/2705.png'
- const informationSource = 'https://statics.teams.microsoft.com/evergreen-assets/emojioneassets/assets-png/2139.png'
- const bangBang = 'https://statics.teams.microsoft.com/evergreen-assets/emojioneassets/assets-png/203c.png'
- const warning = 'https://statics.teams.microsoft.com/evergreen-assets/emojioneassets/assets-png/26a0.png'
-
- var text = ''
-
- switch (event) {
-
- case "github:pull_request:opened:handled":
- case "gitlab:merge_request:opened:handled":
- case "bitbucket:pullrequest:created:opened:handled":
- text = `PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) opened in [${meta.repoName}](${meta.repoUrl})`
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:synchronize:handled":
- case "bitbucket:pullrequest:updated:opened:handled":
- case "gitlab:merge_request:updated:handled":
- text = `PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) updated in [${meta.repoName}](${meta.repoUrl})`
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:pullrequest:fulfilled:handled":
- case "bitbucket:pullrequest:rejected:handled":
- case "github:pull_request:closed:handled":
- case "gitlab:merge_request:closed:handled":
- text = `PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) closed in [${meta.repoName}](${meta.repoUrl})`
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:deploy":
- text = `REST pullrequest deploy trigger \`${meta.pullrequestTitle}\``
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "github:delete:handled":
- case "gitlab:remove:handled":
- case "bitbucket:delete:handled":
- text = `deleted in \`${meta.branchName}\``
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:remove:receive":
- text = `REST remove trigger \`${meta.branchName}\``
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:repo:push:handled":
- case "github:push:handled":
- case "gitlab:push:handled":
- text = `[${meta.branchName}](${meta.repoUrl}/tree/${meta.branchName})`
- if (meta.shortSha){
- text = `${text} ([${meta.shortSha}](${meta.commitUrl}))`
- }
- text = `${text} pushed in [${meta.repoFullName}](${meta.repoUrl})`
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "gitlab:push:skipped":
- case "github:push:skipped":
- case "bitbucket:push:skipped":
- text = `[${meta.branchName}](${meta.repoUrl}/tree/${meta.branchName})`
- if (meta.shortSha){
- text = `${text} ([${meta.shortSha}](${meta.commitUrl}))`
- }
- text = `${text} pushed in [${meta.repoFullName}](${meta.repoUrl}) *deployment skipped*`
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "api:deployEnvironmentBranch":
- case "api:deployEnvironmentLatest":
- text = `API deploy trigger \`${meta.branchName}\``
- if (meta.shortSha) {
- text = `${text} (${meta.shortSha})`
- }
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:deploy:receive":
- text = `REST deploy trigger \`${meta.branchName}\``
- if (meta.shortSha) {
- text = `${text} (${meta.shortSha})`
- }
- sendToMicrosoftTeams(project, text, '#E8E8E8', informationSource, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:promote:receive":
- text = `REST promote trigger \`${meta.branchName}\` -> \`${meta.promoteSourceEnvironment}\``
- sendToMicrosoftTeams(project, text, 'gold', warning, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:finished":
- case "task:remove-openshift-resources:finished":
- case "task:builddeploy-openshift:complete":
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` complete.`
- if (meta.logLink){
- text = `${text} [Logs](${meta.logLink})
`
- }
- text = `${text}${meta.route}
${meta.routes.join("
")}`
- sendToMicrosoftTeams(project, text, 'lawngreen', whiteCheckMark, channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:remove":
- text = `REST pullrequest remove trigger \`${meta.pullrequestNumber}\``
- sendToMicrosoftTeams(project, text, 'lawngreen', whiteCheckMark, channelWrapperLogs, msg, appId)
- break;
-
- case "task:remove-openshift:finished":
- case "task:remove-kubernetes:finished":
- text = `remove \`${meta.openshiftProject}\``
- sendToMicrosoftTeams(project, text, 'lawngreen', whiteCheckMark, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:retry":
- case "task:remove-openshift:retry":
- case "task:remove-kubernetes:retry":
- case "task:remove-openshift-resources:retry":
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` failed.`
- if (meta.logLink){
- text = `${text} ${meta.logLink}`
- }
- sendToMicrosoftTeams(project, message, 'gold', warning, channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:error":
- case "task:remove-openshift:error":
- case "task:remove-kubernetes:error":
- case "task:remove-openshift-resources:error":
- case "task:builddeploy-openshift:failed":
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` failed.`
- if (meta.logLink){
- text = `${text} ${meta.logLink}`
- }
- sendToMicrosoftTeams(project, text, 'red', bangBang, channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:closed:CannotDeleteProductionEnvironment":
- case "github:push:CannotDeleteProductionEnvironment":
- case "bitbucket:repo:push:CannotDeleteProductionEnvironment":
- case "gitlab:push:CannotDeleteProductionEnvironment":
- case "rest:remove:CannotDeleteProductionEnvironment":
- text = `*[${meta.name}]* \`${meta.branchName}\` not deleted. ${meta.error}`
- sendToMicrosoftTeams(project, message, 'gold', warning, channelWrapperLogs, msg, appId)
- break;
- default:
- //since there's no single point of acknowlegement of the msg, we need to keep track of whether we've handled the message
- let eventHandledAsProblem = dispatchProblemEventToTeams(event, project, message, channelWrapperLogs, msg, appId, bangBang);
- if(!eventHandledAsProblem) {
- return channelWrapperLogs.ack(msg);
- }
- }
-}
-
-const dispatchProblemEventToTeams = (event, project, message, channelWrapperLogs, msg, appId, errorEmoji) => {
- const problemEvent = parseProblemNotification(event);
- if(problemEvent.isProblem && problemEvent.eventType == 'insert') {
- sendToMicrosoftTeams(project, message, 'red', errorEmoji, channelWrapperLogs, msg, appId, 'PROBLEM', problemEvent.severityLevel)
- return true;
- }
- return false;
-};
-
-const sendToMicrosoftTeams = async (project, message, color, emoji, channelWrapperLogs, msg, appId, contentType = 'DEPLOYMENT', severityLevel = 'NONE') => {
- let projectMicrosoftTeamsNotifications;
- try {
- projectMicrosoftTeamsNotifications = await getMicrosoftTeamsInfoForProject(project, contentType)
- }
- catch (error) {
- logger.error(`No Microsoft Teams information found, error: ${error}`)
- return channelWrapperLogs.ack(msg)
- }
-
- projectMicrosoftTeamsNotifications.forEach(projectMicrosoftTeams => {
- const notificationThresholdMet = notificationContentTypeToInt(projectMicrosoftTeams.notificationSeverityThreshold) <= notificationContentTypeToInt(severityLevel);
- if(contentType == 'PROBLEM' && !notificationThresholdMet) { return; } //go to next iteration
-
- const { webhook } = projectMicrosoftTeams;
- const webhookUrl = new URL(webhook);
-
- var data = JSON.stringify(
- {
- "@type": "MessageCard",
- "@context": "http://schema.org/extensions",
- "summary": message,
- "title": project,
- "themeColor": color,
- "sections": [
- {
- "activityText": message,
- "activityImage": emoji
- }
- ]
- }
- );
-
- var options = {
- hostname: webhookUrl.host,
- port: webhookUrl.port,
- path: webhookUrl.pathname,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- }
- };
-
- var req = http.request(options, function(res) {
- res.setEncoding('utf8');
- });
-
- req.on('error', function(e) {
- logger.error(`problem with request: ${e.message}`);
- });
- req.end(data);
- });
- channelWrapperLogs.ack(msg)
- return
-}
diff --git a/services/logs2microsoftteams/tsconfig.json b/services/logs2microsoftteams/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2microsoftteams/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
diff --git a/services/logs2rocketchat/.babelrc b/services/logs2rocketchat/.babelrc
deleted file mode 100644
index 5eba7fc969..0000000000
--- a/services/logs2rocketchat/.babelrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "presets": [
- "presets": [["latest-node", { "target": "10" }]]
- ],
- "plugins": [
- "transform-es2015-modules-commonjs"
- ]
-}
diff --git a/services/logs2rocketchat/.gitignore b/services/logs2rocketchat/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2rocketchat/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2rocketchat/Dockerfile b/services/logs2rocketchat/Dockerfile
deleted file mode 100644
index 6251f1df82..0000000000
--- a/services/logs2rocketchat/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2rocketchat/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2rocketchat/README.md b/services/logs2rocketchat/README.md
deleted file mode 100644
index 59358bccfa..0000000000
--- a/services/logs2rocketchat/README.md
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to Rocket.Chat (`logs2rocketchat`)
-
-Watches all the Lagoon logs and checks for events that should trigger a
-Rocket.Chat message. Each log message is tied to a Lagoon project, and
-channel configuration for that project is retrieved from the Lagoon API.
-
-Examples of events that might trigger a message: GitHub pull request opened, a
-new build for a Lagoon project environent has started, a task was completed.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:rocketchat`
-* Produces: `lagoon-logs:rocketchat`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2rocketchat/package.json b/services/logs2rocketchat/package.json
deleted file mode 100644
index 39b3243684..0000000000
--- a/services/logs2rocketchat/package.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "name": "logs2rocketchat",
- "version": "0.0.1",
- "description": "lagoon handler for webhooks",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": [
- "../../node-packages/commons/dist/"
- ],
- "watch": [
- "src",
- "../../node-packages/"
- ],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "amqp-connection-manager": "^1.3.5"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2rocketchat/src/index.ts b/services/logs2rocketchat/src/index.ts
deleted file mode 100644
index 2feaa363fd..0000000000
--- a/services/logs2rocketchat/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:rocketchat', {durable: true}),
- channel.bindQueue('lagoon-logs:rocketchat', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:rocketchat', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2rocketchat/src/readFromRabbitMQ.ts b/services/logs2rocketchat/src/readFromRabbitMQ.ts
deleted file mode 100644
index 22972daee0..0000000000
--- a/services/logs2rocketchat/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import { URL } from 'url';
-import http from 'https';
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { getRocketChatInfoForProject } from '@lagoon/commons/dist/api';
-import { notificationIntToContentType, notificationContentTypeToInt, parseProblemNotification } from '@lagoon/commons/dist/notificationCommons';
-
-export async function readFromRabbitMQ (msg: ConsumeMessage, channelWrapperLogs: ChannelWrapper): Promise {
- const logMessage = JSON.parse(msg.content.toString())
-
- const {
- severity,
- project,
- uuid,
- event,
- meta,
- message
- } = logMessage
-
- const appId = msg.properties.appId || ""
-
- logger.verbose(`received ${event} for project ${project}`)
-
- var text
-
- switch (event) {
-
- case "github:pull_request:opened:handled":
- case "gitlab:merge_request:opened:handled":
- case "bitbucket:pullrequest:created:opened:handled":
- text = `*[${meta.projectName}]* PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) opened in [${meta.repoName}](${meta.repoUrl})`
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:synchronize:handled":
- case "bitbucket:pullrequest:updated:opened:handled":
- case "gitlab:merge_request:updated:handled":
- text = `*[${meta.projectName}]* PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) updated in [${meta.repoName}](${meta.repoUrl})`
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:pullrequest:fulfilled:handled":
- case "bitbucket:pullrequest:rejected:handled":
- case "github:pull_request:closed:handled":
- case "gitlab:merge_request:closed:handled":
- text = `*[${meta.projectName}]* PR [#${meta.pullrequestNumber} (${meta.pullrequestTitle})](${meta.pullrequestUrl}) closed in [${meta.repoName}](${meta.repoUrl})`
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:deploy":
- text = `*[${meta.projectName}]* REST pullrequest deploy trigger \`${meta.pullrequestTitle}\``
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "github:delete:handled":
- case "gitlab:remove:handled":
- case "bitbucket:delete:handled":
- case "api:deleteEnvironment":
- text = `*[${meta.projectName}]* delete trigger \`${meta.environmentName}\``
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "rest:remove:receive":
- text = `*[${meta.projectName}]* REST remove trigger \`${meta.branchName}\``
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "bitbucket:repo:push:handled":
- case "github:push:handled":
- case "gitlab:push:handled":
- text = `*[${meta.projectName}]* [${meta.branchName}](${meta.repoUrl}/tree/${meta.branchName})`
- if (meta.shortSha){
- text = `${text} ([${meta.shortSha}](${meta.commitUrl}))`
- }
- text = `${text} pushed in [${meta.repoFullName}](${meta.repoUrl})`
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "gitlab:push:skipped":
- case "github:push:skipped":
- case "bitbucket:push:skipped":
- text = `*[${meta.projectName}]* [${meta.branchName}](${meta.repoUrl}/tree/${meta.branchName})`
- if (meta.shortSha){
- text = `${text} ([${meta.shortSha}](${meta.commitUrl}))`
- }
- text = `${text} pushed in [${meta.repoFullName}](${meta.repoUrl}) *deployment skipped*`
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "api:deployEnvironmentLatest":
- case "api:deployEnvironmentBranch":
- text = `*[${meta.projectName}]* API deploy trigger \`${meta.branchName}\``
- if (meta.shortSha) {
- text = `${text} (${meta.shortSha})`
- }
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "rest:deploy:receive":
- text = `*[${meta.projectName}]* REST deploy trigger \`${meta.branchName}\``
- if (meta.shortSha) {
- text = `${text} (${meta.shortSha})`
- }
- sendToRocketChat(project, text, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "rest:promote:receive":
- text = `*[${meta.projectName}]* REST promote trigger \`${meta.branchName}\` -> \`${meta.promoteSourceEnvironment}\``
- sendToRocketChat(project, text, 'gold', ':warning:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:finished":
- case "task:remove-openshift-resources:finished":
- case "task:builddeploy-openshift:complete":
- case "task:builddeploy-kubernetes:complete":
- text = `*[${meta.projectName}]* `
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` complete.`
- if (meta.logLink){
- text = `${text} [Logs](${meta.logLink})\n`
- }
- text = `${text}\n ${meta.route}\n`
- if (meta.routes) {
- text = `${text}\n ${meta.routes.join("\n")}`
- }
- sendToRocketChat(project, text, 'lawngreen', ':white_check_mark:', channelWrapperLogs, msg, appId)
- break;
-
- case "rest:pullrequest:remove":
- text = `*[${meta.projectName}]* REST pullrequest remove trigger \`${meta.pullrequestNumber}\``
- sendToRocketChat(project, text, 'lawngreen', ':white_check_mark:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:remove-openshift:finished":
- case "task:remove-kubernetes:finished":
- text = `*[${meta.projectName}]* remove \`${meta.openshiftProject}\``
- sendToRocketChat(project, text, 'lawngreen', ':white_check_mark:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:retry":
- case "task:remove-openshift:retry":
- case "task:remove-kubernetes:retry":
- case "task:remove-openshift-resources:retry":
- text = `*[${meta.projectName}]*`
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` failed.`
- if (meta.logLink){
- text = `${text} ${meta.logLink}`
- }
- sendToRocketChat(project, message, 'gold', ':warning:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:error":
- case "task:remove-openshift:error":
- case "task:remove-kubernetes:error":
- case "task:remove-openshift-resources:error":
- case "task:builddeploy-kubernetes:failed":
- case "task:builddeploy-openshift:failed":
- text = `*[${meta.projectName}]*`
- if (meta.shortSha) {
- text = `${text} \`${meta.branchName}\` (${meta.shortSha})`
- } else {
- text = `${text} \`${meta.branchName}\``
- }
- text = `${text} Build \`${meta.buildName}\` failed.`
- if (meta.logLink){
- text = `${text} [Logs](${meta.logLink})\n`
- }
- sendToRocketChat(project, text, 'red', ':bangbang:', channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:closed:CannotDeleteProductionEnvironment":
- case "github:push:CannotDeleteProductionEnvironment":
- case "bitbucket:repo:push:CannotDeleteProductionEnvironment":
- case "gitlab:push:CannotDeleteProductionEnvironment":
- case "rest:remove:CannotDeleteProductionEnvironment":
- text = `*[${meta.name}]* \`${meta.branchName}\` not deleted. ${meta.error}`
- sendToRocketChat(project, message, 'gold', ':warning:', channelWrapperLogs, msg, appId)
- break;
-
- default:
- //since there's no single point of acknowlegement of the msg, we need to keep track of whether we've handled the message
- let eventHandledAsProblem = dispatchProblemEventToRocketChat(event, project, message, channelWrapperLogs, msg, appId);
- if(!eventHandledAsProblem) {
- return channelWrapperLogs.ack(msg);
- }
- }
-
-}
-
-const dispatchProblemEventToRocketChat = (event, project, message, channelWrapperLogs, msg, appId) => {
- const problemEvent = parseProblemNotification(event);
- if(problemEvent.isProblem && problemEvent.eventType == 'insert') {
- sendToRocketChat(project, message, 'red', ':warning:', channelWrapperLogs, msg, appId, 'PROBLEM', problemEvent.severityLevel)
- return true;
- }
- return false;
-};
-
-const sendToRocketChat = async (project, message, color, emoji, channelWrapperLogs, msg, appId, contentType = 'DEPLOYMENT', severityLevel = 'NONE') => {
- let projectRocketChats;
- try {
- projectRocketChats = await getRocketChatInfoForProject(project, contentType)
- }
- catch (error) {
- logger.error(`No RocketChat information found, error: ${error}`)
- return channelWrapperLogs.ack(msg)
- }
- projectRocketChats.forEach(async (projectRocketChat) => {
-
- const notificationThresholdMet = notificationContentTypeToInt(projectRocketChat.notificationSeverityThreshold) <= notificationContentTypeToInt(severityLevel);
- if(contentType == 'PROBLEM' && !notificationThresholdMet) { return; } //go to next iteration
-
- const { channel, webhook } = projectRocketChat;
- const rocketchat = new URL(webhook);
-
- var data = JSON.stringify({
- channel: `#${channel}`,
- attachments: [{
- text: `${emoji} ${message}`,
- color: color,
- fields: [
- {
- "short": true,
- "title": "Source",
- "value": appId
- }
- ],
- }]
- });
-
- var options = {
- hostname: rocketchat.hostname,
- port: rocketchat.port,
- path: rocketchat.pathname,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Content-Length': data.length
- }
- };
-
- var req = http.request(options, function(res) {
- res.setEncoding('utf8');
- });
-
- req.on('error', function(e) {
- logger.error(`problem with request: ${e.message}`);
- });
- req.write(data);
- req.end();
- });
- channelWrapperLogs.ack(msg)
- return
-}
diff --git a/services/logs2rocketchat/tsconfig.json b/services/logs2rocketchat/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2rocketchat/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
diff --git a/services/logs2s3/.gitignore b/services/logs2s3/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2s3/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2s3/Dockerfile b/services/logs2s3/Dockerfile
deleted file mode 100644
index 7ceda0f379..0000000000
--- a/services/logs2s3/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2s3/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2s3/README.md b/services/logs2s3/README.md
deleted file mode 100644
index 4323a085cb..0000000000
--- a/services/logs2s3/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to S3 (`logs2s3`)
-
-Watches all the Lagoon logs and checks for events that should trigger a push to S3. Each log message is tied to a Lagoon project, environment or task.
-
-This is a requirement for Build and Task logs within Lagoon.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:s3`
-* Produces: `lagoon-logs:s3`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2s3/package.json b/services/logs2s3/package.json
deleted file mode 100644
index f5972929ae..0000000000
--- a/services/logs2s3/package.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "name": "logs2s3",
- "version": "0.0.1",
- "description": "lagoon handler for s3 storage",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": [
- "../../node-packages/commons/dist/"
- ],
- "watch": [
- "src",
- "../../node-packages/"
- ],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "@slack/client": "^4.12.0",
- "aws-sdk": "^2.378.0",
- "amqp-connection-manager": "^1.3.5"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2s3/src/index.ts b/services/logs2s3/src/index.ts
deleted file mode 100644
index 7ef9dcf4d0..0000000000
--- a/services/logs2s3/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:s3', {durable: true}),
- channel.bindQueue('lagoon-logs:s3', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:s3', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2s3/src/readFromRabbitMQ.ts b/services/logs2s3/src/readFromRabbitMQ.ts
deleted file mode 100644
index 29003f9bf0..0000000000
--- a/services/logs2s3/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import S3 from 'aws-sdk/clients/s3';
-import sha1 from 'sha1';
-
-const accessKeyId = process.env.S3_FILES_ACCESS_KEY_ID || 'minio'
-const secretAccessKey = process.env.S3_FILES_SECRET_ACCESS_KEY || 'minio123'
-const bucket = process.env.S3_FILES_BUCKET || 'lagoon-files'
-const region = process.env.S3_FILES_REGION
-const s3Origin = process.env.S3_FILES_HOST || 'http://docker.for.mac.localhost:9000'
-
-const config = {
- origin: s3Origin,
- accessKeyId: accessKeyId,
- secretAccessKey: secretAccessKey,
- region: region,
- bucket: bucket
-};
-
-const s3Client = new S3({
- endpoint: config.origin,
- accessKeyId: config.accessKeyId,
- secretAccessKey: config.secretAccessKey,
- region: config.region,
- params: {
- Bucket: config.bucket
- },
- s3ForcePathStyle: true,
- signatureVersion: 'v4'
-});
-
-const makeSafe = string => string.toLocaleLowerCase().replace(/[^0-9a-z-]/g,'-')
-
-export async function readFromRabbitMQ(
- msg: ConsumeMessage,
- channelWrapperLogs: ChannelWrapper
-): Promise {
- const logMessage = JSON.parse(msg.content.toString());
-
- const { severity, project, uuid, event, meta, message } = logMessage;
-
-
- switch (event) {
- // handle builddeploy build logs from lagoon builds
- case String(event.match(/^build-logs:builddeploy-kubernetes:.*/)):
- logger.verbose(`received ${event} for project ${project} environment ${meta.branchName} - name:${meta.jobName}, remoteId:${meta.remoteId}`);
- await s3Client.putObject({
- Bucket: bucket,
- Key: 'buildlogs/'+project+'/'+meta.branchName+'/'+meta.jobName+'-'+meta.remoteId+'.txt',
- ContentType: 'text/plain',
- Body: Buffer.from(message, 'binary')
- }).promise();
-
- channelWrapperLogs.ack(msg);
- break;
- // handle tasks events for tasks logs
- // yes this says build-logs but it should be task-logs, will be fixed in controller and phased out at some stage
- // the build-logs is a flow on from days past
- case String(event.match(/^build-logs:job-kubernetes:.*/)):
- case String(event.match(/^task-logs:job-kubernetes:.*/)):
- if (meta.environment) {
- // this value comes back as the actual environment/branch name
- // it needs to be made safe just in case
- var environmentName = makeSafe(meta.environment)
- var overlength = 58 - project.length;
- if ( environmentName.length > overlength ) {
- var hash = sha1(environmentName).substring(0,4)
- environmentName = environmentName.substring(0, overlength-5)
- environmentName = environmentName.concat('-' + hash)
- }
- // if the environment is in the data, then save the log to the environments directory
- // some versions of the controller don't send this value in the log meta
- // the resolver in the api also knows to check in both locations when trying to load logs
- logger.verbose(`received ${event} for project ${project} environment ${environmentName} - id:${meta.task.id}, remoteId:${meta.remoteId}`);
- await s3Client.putObject({
- Bucket: bucket,
- Key: 'tasklogs/'+project+'/'+environmentName+'/'+meta.task.id+'-'+meta.remoteId+'.txt',
- ContentType: 'text/plain',
- Body: Buffer.from(message, 'binary')
- }).promise();
- } else {
- logger.verbose(`received ${event} for project ${project} - id:${meta.task.id}, remoteId:${meta.remoteId}`);
- await s3Client.putObject({
- Bucket: bucket,
- Key: 'tasklogs/'+project+'/'+meta.task.id+'-'+meta.remoteId+'.txt',
- ContentType: 'text/plain',
- Body: Buffer.from(message, 'binary')
- }).promise();
- }
- channelWrapperLogs.ack(msg);
- break;
- default:
- return channelWrapperLogs.ack(msg);
- }
-}
diff --git a/services/logs2s3/tsconfig.json b/services/logs2s3/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2s3/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
diff --git a/services/logs2slack/.gitignore b/services/logs2slack/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2slack/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2slack/Dockerfile b/services/logs2slack/Dockerfile
deleted file mode 100644
index 9aaa1a761f..0000000000
--- a/services/logs2slack/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2slack/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2slack/README.md b/services/logs2slack/README.md
deleted file mode 100644
index c2c05e7a69..0000000000
--- a/services/logs2slack/README.md
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to Slack (`logs2slack`)
-
-Watches all the Lagoon logs and checks for events that should trigger a Slack
-message. Each log message is tied to a Lagoon project, and channel configuration
-for that project is retrieved from the Lagoon API.
-
-Examples of events that might trigger a message: GitHub pull request opened, a
-new build for a Lagoon project environent has started, a task was completed.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:slack`
-* Produces: `lagoon-logs:slack`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2slack/package.json b/services/logs2slack/package.json
deleted file mode 100644
index 402c985ee1..0000000000
--- a/services/logs2slack/package.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "name": "logs2slack",
- "version": "0.9.0",
- "description": "lagoon handler for webhooks",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": [
- "../../node-packages/commons/dist/"
- ],
- "watch": [
- "src",
- "../../node-packages/"
- ],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "@slack/client": "^4.12.0",
- "amqp-connection-manager": "^1.3.5"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2slack/src/index.ts b/services/logs2slack/src/index.ts
deleted file mode 100644
index 31a1099f15..0000000000
--- a/services/logs2slack/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:slack', {durable: true}),
- channel.bindQueue('lagoon-logs:slack', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:slack', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2slack/src/readFromRabbitMQ.ts b/services/logs2slack/src/readFromRabbitMQ.ts
deleted file mode 100644
index 5bf8785572..0000000000
--- a/services/logs2slack/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { IncomingWebhook } from '@slack/client';
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { getSlackinfoForProject } from '@lagoon/commons/dist/api';
-import { notificationIntToContentType, notificationContentTypeToInt, parseProblemNotification } from '@lagoon/commons/dist/notificationCommons';
-
-export async function readFromRabbitMQ (msg: ConsumeMessage, channelWrapperLogs: ChannelWrapper): Promise {
- const logMessage = JSON.parse(msg.content.toString())
-
- const {
- severity,
- project,
- uuid,
- event,
- meta,
- message
- } = logMessage
-
- const appId = msg.properties.appId || ""
-
- logger.verbose(`received ${event} for project ${project}`)
-
- switch (event) {
-
- case "github:pull_request:closed:handled":
- case "github:pull_request:opened:handled":
- case "github:pull_request:synchronize:handled":
- case "github:delete:handled":
- case "github:push:handled":
- case "bitbucket:repo:push:handled":
- case "bitbucket:pullrequest:created:handled":
- case "bitbucket:pullrequest:updated:handled":
- case "bitbucket:pullrequest:fulfilled:handled":
- case "bitbucket:pullrequest:rejected:handled":
- case "gitlab:push:handled":
- case "gitlab:merge_request:opened:handled":
- case "gitlab:merge_request:updated:handled":
- case "gitlab:merge_request:closed:handled":
- case "rest:deploy:receive":
- case "rest:remove:receive":
- case "rest:promote:receive":
- case "api:deployEnvironmentLatest":
- case "api:deployEnvironmentBranch":
- case "api:deleteEnvironment":
- case "github:push:skipped":
- case "gitlab:push:skipped":
- case "bitbucket:push:skipped":
- sendToSlack(project, message, '#E8E8E8', ':information_source:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:finished":
- case "task:remove-openshift:finished":
- case "task:remove-kubernetes:finished":
- case "task:remove-openshift-resources:finished":
- case "task:builddeploy-openshift:complete":
- case "task:builddeploy-kubernetes:complete":
- sendToSlack(project, message, 'good', ':white_check_mark:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:retry":
- case "task:remove-openshift:retry":
- case "task:remove-kubernetes:retry":
- case "task:remove-openshift-resources:retry":
- sendToSlack(project, message, 'warning', ':warning:', channelWrapperLogs, msg, appId)
- break;
-
- case "task:deploy-openshift:error":
- case "task:remove-openshift:error":
- case "task:remove-kubernetes:error":
- case "task:remove-openshift-resources:error":
- case "task:builddeploy-openshift:failed":
- case "task:builddeploy-kubernetes:failed":
- sendToSlack(project, message, 'danger', ':bangbang:', channelWrapperLogs, msg, appId)
- break;
-
- case "github:pull_request:closed:CannotDeleteProductionEnvironment":
- case "github:push:CannotDeleteProductionEnvironment":
- case "bitbucket:repo:push:CannotDeleteProductionEnvironment":
- case "gitlab:push:CannotDeleteProductionEnvironment":
- case "rest:remove:CannotDeleteProductionEnvironment":
- sendToSlack(project, message, 'warning', ':warning:', channelWrapperLogs, msg, appId)
- break;
- default:
- //since there's no single point of acknowlegement of the msg, we need to keep track of whether we've handled the message
- let eventHandledAsProblem = dispatchProblemEventToSlack(event, project, message, channelWrapperLogs, msg, appId);
- if(!eventHandledAsProblem) {
- return channelWrapperLogs.ack(msg);
- }
- }
-}
-
-const dispatchProblemEventToSlack = (event, project, message, channelWrapperLogs, msg, appId) => {
- const problemEvent = parseProblemNotification(event);
- if(problemEvent.isProblem) {
- const isNewProblem = problemEvent.eventType == 'insert';
- if(isNewProblem) {
- sendToSlack(project, message, 'warning', ':warning:', channelWrapperLogs, msg, appId, 'PROBLEM', problemEvent.severityLevel)
- return true;
- }
- }
- return false;
-};
-
-const sendToSlack = async (project, message, color, emoji, channelWrapperLogs, msg, appId, contentType = 'DEPLOYMENT', severityLevel = 'NONE') => {
-
- let projectSlacks;
- try {
- projectSlacks = await getSlackinfoForProject(project, contentType)
- }
- catch (error) {
- logger.error(`No Slack information found, error: ${error}`)
- return channelWrapperLogs.ack(msg)
- }
- projectSlacks.forEach(async (projectSlack) => {
-
- const notificationThresholdMet = notificationContentTypeToInt(projectSlack.notificationSeverityThreshold) <= notificationContentTypeToInt(severityLevel);
- if(contentType == 'PROBLEM' && !notificationThresholdMet) { return; } //go to next iteration
-
- await new IncomingWebhook(projectSlack.webhook, {
- channel: projectSlack.channel,
- }).send({
- attachments: [{
- text: `${emoji} ${message}`,
- color: color,
- "mrkdwn_in": ["pretext", "text", "fields"],
- footer: appId
- }]
- });
- });
- channelWrapperLogs.ack(msg)
- return
-}
diff --git a/services/logs2slack/tsconfig.json b/services/logs2slack/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2slack/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
diff --git a/services/logs2webhook/.gitignore b/services/logs2webhook/.gitignore
deleted file mode 100644
index f06235c460..0000000000
--- a/services/logs2webhook/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-node_modules
-dist
diff --git a/services/logs2webhook/Dockerfile b/services/logs2webhook/Dockerfile
deleted file mode 100644
index b3c757e6da..0000000000
--- a/services/logs2webhook/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-ARG LAGOON_GIT_BRANCH
-ARG IMAGE_REPO
-ARG UPSTREAM_REPO
-ARG UPSTREAM_TAG
-# STAGE 1: Loading Image lagoon-node-packages-builder which contains node packages shared by all Node Services
-FROM ${IMAGE_REPO:-lagoon}/yarn-workspace-builder as yarn-workspace-builder
-
-# STAGE 2: specific service Image
-FROM ${UPSTREAM_REPO:-uselagoon}/node-16:${UPSTREAM_TAG:-latest}
-
-ARG LAGOON_VERSION
-ENV LAGOON_VERSION=$LAGOON_VERSION
-
-# Copying generated node_modules from the first stage
-COPY --from=yarn-workspace-builder /app /app
-
-# Setting the workdir to the service, all following commands will run from here
-WORKDIR /app/services/logs2webhook/
-
-# Copying the .env.defaults into the Workdir, as the dotenv system searches within the workdir for it
-COPY --from=yarn-workspace-builder /app/.env.defaults .
-
-# Copying files from our service
-COPY . .
-
-# Verify that all dependencies have been installed via the yarn-workspace-builder
-RUN yarn check --verify-tree
-
-# Making sure we run in production
-ENV NODE_ENV production
-
-RUN yarn build
-
-CMD ["yarn", "start"]
diff --git a/services/logs2webhook/README.md b/services/logs2webhook/README.md
deleted file mode 100644
index a558778132..0000000000
--- a/services/logs2webhook/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-This service is part of amazee.io Lagoon, a Docker build and deploy system for
-OpenShift & Kubernetes. Please reference our [documentation] for detailed
-information on using, developing, and administering Lagoon.
-
-# Logs to Webhook (`logs2webhook`)
-
-Watches all the Lagoon logs and checks for events that should trigger a webhook call. Each log message is tied to a Lagoon project, and channel configuration
-for that project is retrieved from the Lagoon API.
-
-Examples of events that might trigger a message: GitHub pull request opened, a
-new build for a Lagoon project environent has started, a task was completed.
-
-## Technology
-
-* Node.js
-* Message Queue
-
-## Related Services
-
-* API [***dependency***]
-* RabbitMQ [***dependency***]
-
-## Message Queues
-
-* Consumes: `lagoon-logs`, `lagoon-logs:webhook`
-* Produces: `lagoon-logs:webhook`
-
-[documentation]: https://docs.lagoon.sh/
diff --git a/services/logs2webhook/package.json b/services/logs2webhook/package.json
deleted file mode 100644
index ac54a3287e..0000000000
--- a/services/logs2webhook/package.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "name": "logs2webhook",
- "version": "0.9.0",
- "description": "lagoon handler for webhooks",
- "author": "amazee.io (http://www.amazee.io)",
- "main": "dist/index.js",
- "scripts": {
- "build": "tsc --build",
- "start": "node dist/index",
- "dev": "mkdir -p ../../node-packages/commons/dist && NODE_ENV=development nodemon"
- },
- "nodemonConfig": {
- "ignore": [
- "../../node-packages/commons/dist/"
- ],
- "watch": [
- "src",
- "../../node-packages/"
- ],
- "ext": "js,ts,json",
- "exec": "yarn build --incremental && yarn start --inspect=0.0.0.0:9229"
- },
- "license": "MIT",
- "dependencies": {
- "@lagoon/commons": "4.0.0",
- "@slack/client": "^4.12.0",
- "amqp-connection-manager": "^1.3.5"
- },
- "devDependencies": {
- "@types/amqp-connection-manager": "^2.0.10",
- "nodemon": "^1.12.1",
- "typescript": "^3.9.3"
- }
-}
diff --git a/services/logs2webhook/src/index.ts b/services/logs2webhook/src/index.ts
deleted file mode 100644
index 74577bea8c..0000000000
--- a/services/logs2webhook/src/index.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import amqp, { ChannelWrapper } from 'amqp-connection-manager';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import { readFromRabbitMQ } from './readFromRabbitMQ';
-
-const rabbitmqHost = process.env.RABBITMQ_HOST || "broker"
-const rabbitmqUsername = process.env.RABBITMQ_USERNAME || "guest"
-const rabbitmqPassword = process.env.RABBITMQ_PASSWORD || "guest"
-// @ts-ignore
-const connection = amqp.connect([`amqp://${rabbitmqUsername}:${rabbitmqPassword}@${rabbitmqHost}`], { json: true });
-
-connection.on('connect', ({ url }) => logger.verbose('Connected to %s', url, { action: 'connected', url }));
-// @ts-ignore
-connection.on('disconnect', params => logger.error('Not connected, error: %s', params.err.code, { action: 'disconnected', reason: params }));
-
-// Cast any to ChannelWrapper to get type-safetiness through our own code
-const channelWrapperLogs: ChannelWrapper = connection.createChannel({
- setup: channel => {
- return Promise.all([
- channel.assertExchange('lagoon-logs', 'direct', {durable: true}),
- channel.assertQueue('lagoon-logs:webhook', {durable: true}),
- channel.bindQueue('lagoon-logs:webhook', 'lagoon-logs', ''),
- channel.prefetch(1),
- channel.consume('lagoon-logs:webhook', msg => readFromRabbitMQ(msg, channelWrapperLogs), {noAck: false}),
- ]);
- }
-});
diff --git a/services/logs2webhook/src/readFromRabbitMQ.ts b/services/logs2webhook/src/readFromRabbitMQ.ts
deleted file mode 100644
index ec09c7172c..0000000000
--- a/services/logs2webhook/src/readFromRabbitMQ.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { ChannelWrapper } from 'amqp-connection-manager';
-import { ConsumeMessage } from 'amqplib';
-import { logger } from '@lagoon/commons/dist/local-logging';
-import {
- getWebhookNotificationInfoForProject,
- getEnvironmentById
-} from '@lagoon/commons/dist/api';
-import {
- notificationIntToContentType,
- notificationContentTypeToInt,
- parseProblemNotification
-} from '@lagoon/commons/dist/notificationCommons';
-import { URL } from 'url';
-import http from 'https';
-
-export async function readFromRabbitMQ(
- msg: ConsumeMessage,
- channelWrapperLogs: ChannelWrapper
-): Promise {
- const logMessage = JSON.parse(msg.content.toString());
-
- const { severity, project, uuid, event, meta, message } = logMessage;
-
- const appId = msg.properties.appId || '';
-
- logger.verbose(`received ${event} for project ${project}`);
-
- switch (event) {
- case 'task:deploy-openshift:finished':
- case 'task:remove-openshift:finished':
- case 'task:remove-kubernetes:finished':
- case 'task:remove-openshift-resources:finished':
- case 'task:builddeploy-openshift:complete':
- case 'task:builddeploy-kubernetes:complete':
- case 'task:deploy-openshift:error':
- case 'task:remove-openshift:error':
- case 'task:remove-kubernetes:error':
- case 'task:remove-openshift-resources:error':
- case 'task:builddeploy-openshift:failed':
- case 'task:builddeploy-kubernetes:failed':
- let payload = {
- type: 'DEPLOYMENT',
- event: event.split(':').pop(),
- project,
- environment: ''
- };
- if (meta && meta.environmentId) {
- const environmentDetails = await getEnvironmentById(meta.environmentId);
- payload.environment = environmentDetails.environmentById.name;
- }
- sendToWebhook(event, project, payload, channelWrapperLogs, msg);
- break;
- default:
- return channelWrapperLogs.ack(msg);
- break;
- }
-}
-
-const sendToWebhook = async (
- event,
- project,
- payload,
- channelWrapperLogs,
- msg
-) => {
- let projectWebhooks;
- try {
- projectWebhooks = await getWebhookNotificationInfoForProject(
- project,
- 'DEPLOYMENT'
- );
- } catch (error) {
- logger.error(`No Webhook information found, error: ${error}`);
- return channelWrapperLogs.ack(msg);
- }
-
- projectWebhooks.forEach(projectWebhook => {
- const { webhook } = projectWebhook;
- const webhookUrl = new URL(webhook);
-
- var data = JSON.stringify(payload);
-
- var options = {
- hostname: webhookUrl.host,
- port: webhookUrl.port,
- path: webhookUrl.pathname,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- }
- };
-
- var req = http.request(options, function(res) {
- res.setEncoding('utf8');
- });
-
- req.on('error', function(e) {
- logger.error(`problem with request: ${e.message}`);
- });
- req.end(data);
- });
-
- channelWrapperLogs.ack(msg);
- return;
-};
diff --git a/services/logs2webhook/tsconfig.json b/services/logs2webhook/tsconfig.json
deleted file mode 100644
index 6ce2574ab8..0000000000
--- a/services/logs2webhook/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./dist"
- },
- "include": ["./src"],
- "references": [
- { "path": "../../node-packages/commons" }
- ]
-}
From 6086a4b99a61147ddbd9f708db00a342597e741d Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 16:01:13 +1100
Subject: [PATCH 06/17] chore: add logs2notifications treeish to makefile
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 68c66c5274..09dad27d72 100644
--- a/Makefile
+++ b/Makefile
@@ -510,7 +510,7 @@ STERN_VERSION = 2.1.17
CHART_TESTING_VERSION = v3.4.0
KIND_IMAGE = kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
TESTS = [nginx,api,features-kubernetes,bulk-deployment,features-kubernetes-2,features-api-variables,active-standby-kubernetes,tasks,drush,drupal-php80,drupal-postgres,python,gitlab,github,bitbucket,node-mongodb,elasticsearch,workflows]
-CHARTS_TREEISH = "main"
+CHARTS_TREEISH = "logs2notifications"
# Symlink the installed kubectl client if the correct version is already
# installed, otherwise downloads it.
From 3ec8af6e49556c10d200cf19e14547e355e1b3cc Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 16:06:25 +1100
Subject: [PATCH 07/17] chore: remove other references to logs2x from yarn
builder
---
images/yarn-workspace-builder/Dockerfile | 6 ------
1 file changed, 6 deletions(-)
diff --git a/images/yarn-workspace-builder/Dockerfile b/images/yarn-workspace-builder/Dockerfile
index cd66e7db1b..9f0fff475f 100644
--- a/images/yarn-workspace-builder/Dockerfile
+++ b/images/yarn-workspace-builder/Dockerfile
@@ -13,12 +13,6 @@ COPY node-packages /app/node-packages
# subdependencies won't be installed
COPY services/api/package.json /app/services/api/
COPY services/auth-server/package.json /app/services/auth-server/
-COPY services/logs2email/package.json /app/services/logs2email/
-COPY services/logs2microsoftteams/package.json /app/services/logs2microsoftteams/
-COPY services/logs2rocketchat/package.json /app/services/logs2rocketchat/
-COPY services/logs2slack/package.json /app/services/logs2slack/
-COPY services/logs2s3/package.json /app/services/logs2s3/
-COPY services/logs2webhook/package.json /app/services/logs2webhook/
COPY services/controllerhandler/package.json /app/services/controllerhandler/
COPY services/webhook-handler/package.json /app/services/webhook-handler/
COPY services/webhooks2tasks/package.json /app/services/webhooks2tasks/
From e5d20239ce23cd23a651e5654719d09b754978b8 Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 23 Mar 2022 16:28:44 +1100
Subject: [PATCH 08/17] chore: remove a reference to logs2s3
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 09dad27d72..a07f1dde9f 100644
--- a/Makefile
+++ b/Makefile
@@ -629,7 +629,7 @@ ifeq ($(ARCH), darwin)
tcp-listen:32080,fork,reuseaddr tcp-connect:target:32080
endif
-KIND_SERVICES = api api-db api-redis auth-server actions-handler broker controllerhandler docker-host drush-alias keycloak keycloak-db logs2notifications logs2s3 webhook-handler webhooks2tasks kubectl-build-deploy-dind local-api-data-watcher-pusher local-git ssh tests ui workflows
+KIND_SERVICES = api api-db api-redis auth-server actions-handler broker controllerhandler docker-host drush-alias keycloak keycloak-db logs2notifications webhook-handler webhooks2tasks kubectl-build-deploy-dind local-api-data-watcher-pusher local-git ssh tests ui workflows
KIND_TESTS = local-api-data-watcher-pusher local-git tests
KIND_TOOLS = kind helm kubectl jq stern
From 6037845e75894b508c1f7bd96980cce371201146 Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 8 Jun 2022 15:34:46 +1000
Subject: [PATCH 09/17] refactor: add tests to logs2notifications and fix up
outputs
---
services/logs2notifications/go.mod | 2 +
services/logs2notifications/go.sum | 4 +
.../internal/handler/email_events.go | 382 ++++++------------
.../internal/handler/main.go | 127 ++++--
.../internal/handler/main_test.go | 179 +++++++-
.../internal/handler/microsoftteams_events.go | 196 +++------
.../internal/handler/rocketchat_events.go | 321 +++++----------
.../internal/handler/s3_events.go | 105 ++---
.../internal/handler/slack_events.go | 198 ++++++---
.../testdata/deleteEnvironment/emailhtml.txt | 1 +
.../testdata/deleteEnvironment/emailplain.txt | 1 +
.../testdata/deleteEnvironment/rocketchat.txt | 1 +
.../testdata/deleteEnvironment/slack.txt | 1 +
.../testdata/deleteEnvironment/teams.txt | 1 +
.../testdata/deployEnvironment/emailhtml.txt | 1 +
.../testdata/deployEnvironment/emailplain.txt | 1 +
.../testdata/deployEnvironment/rocketchat.txt | 1 +
.../testdata/deployEnvironment/slack.txt | 1 +
.../testdata/deployEnvironment/teams.txt | 1 +
.../testdata/deployError/emailhtml.txt | 2 +
.../testdata/deployError/emailplain.txt | 2 +
.../testdata/deployError/rocketchat.txt | 1 +
.../handler/testdata/deployError/slack.txt | 1 +
.../handler/testdata/deployError/teams.txt | 1 +
.../testdata/deployFinished/emailhtml.txt | 10 +
.../testdata/deployFinished/emailplain.txt | 4 +
.../testdata/deployFinished/rocketchat.txt | 4 +
.../handler/testdata/deployFinished/slack.txt | 4 +
.../handler/testdata/deployFinished/teams.txt | 4 +
.../input.deleteEnvironmentGithub.json | 8 +
.../testdata/input.deployEnvironment.json | 9 +
.../handler/testdata/input.deployError.json | 11 +
.../testdata/input.deployFinished.json | 14 +
.../testdata/input.repoPushHandledGithub.json | 12 +
.../testdata/input.repoPushSkippedGithub.json | 12 +
.../handler/testdata/mergeRequestClosed.json | 11 +
.../handler/testdata/mergeRequestOpened.json | 11 +
.../handler/testdata/mergeRequestUpdated.json | 11 +
.../internal/handler/testdata/notDeleted.json | 0
.../testdata/problemNotification.1.json | 12 +
.../testdata/problemNotification.2.json | 12 +
.../handler/testdata/removeFinished.json | 0
.../testdata/repoPushHandled/emailhtml.txt | 1 +
.../testdata/repoPushHandled/emailplain.txt | 1 +
.../testdata/repoPushHandled/rocketchat.txt | 1 +
.../testdata/repoPushHandled/slack.txt | 1 +
.../testdata/repoPushHandled/teams.txt | 1 +
.../testdata/repoPushSkipped/emailhtml.txt | 1 +
.../testdata/repoPushSkipped/emailplain.txt | 1 +
.../testdata/repoPushSkipped/rocketchat.txt | 1 +
.../testdata/repoPushSkipped/slack.txt | 1 +
.../testdata/repoPushSkipped/teams.txt | 1 +
services/logs2notifications/main.go | 85 ++--
53 files changed, 995 insertions(+), 780 deletions(-)
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployError/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployError.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.deployFinished.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
create mode 100644 services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
create mode 100644 services/logs2notifications/internal/handler/testdata/notDeleted.json
create mode 100644 services/logs2notifications/internal/handler/testdata/problemNotification.1.json
create mode 100644 services/logs2notifications/internal/handler/testdata/problemNotification.2.json
create mode 100644 services/logs2notifications/internal/handler/testdata/removeFinished.json
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
create mode 100644 services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
diff --git a/services/logs2notifications/go.mod b/services/logs2notifications/go.mod
index aa3e6ae785..2f16b24448 100644
--- a/services/logs2notifications/go.mod
+++ b/services/logs2notifications/go.mod
@@ -13,7 +13,9 @@ require (
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2
github.com/slack-go/slack v0.9.5
github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
+ gopkg.in/mail.v2 v2.3.1 // indirect
)
// Fixes for AppID
diff --git a/services/logs2notifications/go.sum b/services/logs2notifications/go.sum
index 4892db91f2..7cbfa00037 100644
--- a/services/logs2notifications/go.sum
+++ b/services/logs2notifications/go.sum
@@ -190,9 +190,13 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
+gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/services/logs2notifications/internal/handler/email_events.go b/services/logs2notifications/internal/handler/email_events.go
index e0e805942b..1c2c8cd86d 100644
--- a/services/logs2notifications/internal/handler/email_events.go
+++ b/services/logs2notifications/internal/handler/email_events.go
@@ -2,10 +2,14 @@ package handler
import (
"bytes"
+ "crypto/tls"
"fmt"
"log"
- "net/smtp"
+ "strconv"
+ "strings"
"text/template"
+
+ gomail "gopkg.in/mail.v2"
)
var htmlTemplate = `
@@ -37,311 +41,171 @@ var htmlTemplate = `#%s (%s opened in %s`,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoURL,
- notification.Meta.RepoName,
- )
- plainText += fmt.Sprintf(`[%s] PR #%s - %s opened in %s`,
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.RepoName,
- )
- subject += plainText
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) opened in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} opened in {{.RepoName}}`
case "mergeRequestUpdated":
- mainHTML += fmt.Sprintf(`PR #%s (%s updated in %s`,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoURL,
- notification.Meta.RepoName,
- )
- plainText += fmt.Sprintf(`[%s] PR #%s - %s updated in %s`,
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.RepoName,
- )
- subject += plainText
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) updated in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} updated in {{.RepoName}}`
case "mergeRequestClosed":
- mainHTML += fmt.Sprintf(`PR #%s (%s closed in %s`,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoURL,
- notification.Meta.RepoName,
- )
- plainText += fmt.Sprintf(`[%s] PR #%s - %s closed in %s`,
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.RepoName,
- )
- subject += plainText
+ mainHTMLTpl = `PR #{{.PullrequestNumber}} ({{.PullrequestTitle}}) closed in {{.RepoName}}`
+ plainTextTpl = `[{{.ProjectName}}] PR #{{.PullrequestNumber}} - {{.PullrequestTitle}} closed in {{.RepoName}}`
case "deleteEnvironment":
- mainHTML += fmt.Sprintf("Deleted environment %s
",
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf("[%s] deleted environment %s",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- )
- subject += plainText
+ mainHTMLTpl = `Deleted environment {{.EnvironmentName}}
`
+ plainTextTpl = `[{{.ProjectName}}] deleted environment {{.EnvironmentName}}`
case "repoPushHandled":
- mainHTML += fmt.Sprintf(`%s`,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf(`[%s] %s`,
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- mainHTML += fmt.Sprintf(`%s %s`,
- mainHTML,
- notification.Meta.CommitURL,
- notification.Meta.ShortSha,
- )
- plainText += fmt.Sprintf(`%s (%s)`,
- plainText,
- notification.Meta.ShortSha,
- )
- }
- mainHTML += fmt.Sprintf(`%s pushed in %s`,
- mainHTML,
- notification.Meta.RepoURL,
- notification.Meta.RepoFullName,
- )
- plainText += fmt.Sprintf(`%s pushed in %s`,
- plainText,
- notification.Meta.RepoFullName,
- )
- subject += plainText
+ mainHTMLTpl = `{{.BranchName}}{{ if ne .ShortSha "" }} {{.ShortSha}}{{end}} pushed in {{.RepoFullName}}`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} pushed in {{.RepoFullName}}`
case "repoPushSkipped":
- mainHTML += fmt.Sprintf(`%s`,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf(`[%s] %s`,
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- mainHTML += fmt.Sprintf(`%s %s`,
- mainHTML,
- notification.Meta.CommitURL,
- notification.Meta.ShortSha,
- )
- plainText += fmt.Sprintf(`%s (%s)`,
- plainText,
- notification.Meta.ShortSha,
- )
- }
- mainHTML += fmt.Sprintf(`%s pushed in %s deployment skipped`,
- mainHTML,
- notification.Meta.RepoURL,
- notification.Meta.RepoFullName,
- )
- plainText += fmt.Sprintf(`%s pushed in %s *deployment skipped*`,
- plainText,
- notification.Meta.RepoFullName,
- )
- subject += plainText
+ mainHTMLTpl = `{{.BranchName}}{{ if ne .ShortSha "" }} {{.ShortSha}}{{end}} pushed in {{.RepoFullName}} deployment skipped`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} pushed in {{.RepoFullName}} *deployment skipped*`
case "deployEnvironment":
- mainHTML += fmt.Sprintf("Deployment triggered %s
",
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf("[%s] Deployment triggered on branch %s",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- mainHTML += fmt.Sprintf(`%s (%s)`,
- mainHTML,
- notification.Meta.ShortSha,
- )
- plainText += fmt.Sprintf(`%s (%s)`,
- plainText,
- notification.Meta.ShortSha,
- )
- }
- subject += plainText
+ mainHTMLTpl = `Deployment triggered {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ plainTextTpl = `[{{.ProjectName}}] Deployment triggered on branch {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
case "removeFinished":
- mainHTML += fmt.Sprintf("Remove %s
", notification.Meta.OpenshiftProject)
- plainText += fmt.Sprintf("[%s] remove %s", notification.Meta.ProjectName, notification.Meta.OpenshiftProject)
- subject += plainText
+ mainHTMLTpl = `Remove {{.OpenshiftProject}}
`
+ plainTextTpl = `[{{.ProjectName}] remove {{.OpenshiftProject}}`
case "notDeleted":
- mainHTML += fmt.Sprintf("%s
not deleted.",
- notification.Meta.OpenshiftProject,
- )
- plainText += fmt.Sprintf("[%s] %s not deleted.",
- notification.Meta.ProjectName,
- notification.Meta.OpenshiftProject,
- )
- subject += plainText
- plainText += fmt.Sprintf("%s", notification.Meta.Error)
+ mainHTMLTpl = `{{.OpenshiftProject}}
not deleted.`
+ plainTextTpl = `[{{.ProjectName}] {{.OpenshiftProject}} not deleted. {{.Error}}`
case "deployError":
- mainHTML += fmt.Sprintf("[%s]",
+ mainHTMLTpl = `[{{.ProjectName}}] {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}}
error.
+{{if ne .LogLink ""}} Logs{{end}}`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}} error.
+{{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
+ subject += fmt.Sprintf("[%s] %s Build %s error.",
notification.Meta.ProjectName,
- )
- plainText += fmt.Sprintf("[%s]",
- notification.Meta.ProjectName,
- )
- if notification.Meta.ShortSha != "" {
- mainHTML += fmt.Sprintf(` %s
(%s)`,
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- plainText += fmt.Sprintf(` %s (%s)`,
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- mainHTML += fmt.Sprintf(` %s
`,
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf(` %s`,
- notification.Meta.BranchName,
- )
- }
- mainHTML += fmt.Sprintf(` Build %s
error.`,
- notification.Meta.BuildName,
- )
- plainText += fmt.Sprintf(` Build %s error.`,
+ notification.Meta.BranchName,
notification.Meta.BuildName,
)
- subject += plainText
- if notification.Meta.LogLink != "" {
- mainHTML += fmt.Sprintf(` Logs`,
- notification.Meta.LogLink,
- )
- plainText += fmt.Sprintf(` [Logs](%s)`,
- notification.Meta.LogLink,
- )
- }
case "deployFinished":
- mainHTML += fmt.Sprintf("[%s]",
+ mainHTMLTpl = `[{{.ProjectName}}] {{.BranchName}}
{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}}
complete. {{if ne .LogLink ""}}Logs{{end}}
+
+
+
+
+
`
+ plainTextTpl = `[{{.ProjectName}}] {{.BranchName}}{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build {{.BuildName}} complete. {{if ne .LogLink ""}}[Logs]({{.LogLink}}){{end}}
+{{.Route}}
+{{range .Routes}}{{if ne . $.Route}}{{.}}
+{{end}}{{end}}`
+ subject += fmt.Sprintf("[%s] %s Build %s complete.",
notification.Meta.ProjectName,
- )
- plainText += fmt.Sprintf("[%s]",
- notification.Meta.ProjectName,
- )
- if notification.Meta.ShortSha != "" {
- mainHTML += fmt.Sprintf(`
%s
(%s)`,
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- plainText += fmt.Sprintf(` %s (%s)`,
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- mainHTML += fmt.Sprintf(`
%s
`,
- notification.Meta.BranchName,
- )
- plainText += fmt.Sprintf(` %s`,
- notification.Meta.BranchName,
- )
- }
- mainHTML += fmt.Sprintf(` Build
%s
complete.`,
- notification.Meta.BuildName,
- )
- plainText += fmt.Sprintf(` Build %s complete.`,
+ notification.Meta.BranchName,
notification.Meta.BuildName,
)
- subject += plainText
- if notification.Meta.LogLink != "" {
- mainHTML += fmt.Sprintf(`
Logs`,
- notification.Meta.LogLink,
- )
- plainText += fmt.Sprintf(` [Logs](%s)`,
- notification.Meta.LogLink,
- )
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", "", "", nil
}
- additional += fmt.Sprintf(`
- %s
`,
- notification.Meta.Route,
- notification.Meta.Route,
- )
- plainText += fmt.Sprintf("%s %s\n",
- plainText,
- notification.Meta.Route,
+ mainHTMLTpl = `[{{.ProjectName}}] New problem found for {{.EnvironmentName}}
+ - * Service: {{.ServiceName}}
{{ if ne .Severity "" }}
+ - * Severity: {{.Severity}}{{end}}
{{ if ne .Description "" }}
+ - * Description: {{.Description}}
{{end}}
`
+ plainTextTpl = `[{{.ProjectName}}] New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ subject += fmt.Sprintf("[%s] New problem found for environment %s",
+ notification.Meta.ProjectName,
+ notification.Meta.EnvironmentName,
)
- if len(notification.Meta.Routes) != 0 {
- for _, r := range notification.Meta.Routes {
- if r != notification.Meta.Route {
- additional += fmt.Sprintf(` - %s
`,
- r,
- r,
- )
- plainText += fmt.Sprintf(" %s\n",
- r,
- )
- }
- }
- }
- mainHTML += fmt.Sprintf(`
`)
+ default:
+ return "", "", "", "", "", nil
}
- t, _ := template.New("email").Parse(htmlTemplate)
+ var body bytes.Buffer
+ t, _ := template.New("email").Parse(mainHTMLTpl)
+ t.Execute(&body, notification.Meta)
+ mainHTML += body.String()
+
+ var plainTextBuffer bytes.Buffer
+ t, _ = template.New("email").Parse(plainTextTpl)
+ t.Execute(&plainTextBuffer, notification.Meta)
+ plainText += plainTextBuffer.String()
+ if subject == "" {
+ subject = plainText
+ }
+ return emoji, color, subject, mainHTML, plainText, nil
+}
+
+func (h *Messaging) sendEmailMessage(emoji, color, subject, event, project, emailAddress, mainHTML, plainText string) {
var body bytes.Buffer
- mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
- body.Write([]byte(fmt.Sprintf("From: Lagoon Notifications\nSubject: %s \n%s\n\n", subject, mimeHeaders)))
+ // mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
+ // body.Write([]byte(fmt.Sprintf("From: Lagoon Notifications<%s>\nSubject: %s \n%s\n\n", h.EmailSender, subject, mimeHeaders)))
+ t, _ := template.New("email").Parse(htmlTemplate)
t.Execute(&body, struct {
Color string
Emoji string
Title string
ProjectName string
MainHTML string
- Additional string
}{
Title: plainText,
Color: color,
Emoji: emoji,
- ProjectName: notification.Meta.ProjectName,
+ ProjectName: project,
MainHTML: mainHTML,
- Additional: additional,
})
- // Configuration
- from := "notifications@lagoon.sh"
- password := ""
- to := []string{emailAddress}
- smtpHost := "localhost"
- smtpPort := "1025"
- // Create authentication
- auth := smtp.PlainAuth("", from, password, smtpHost)
- // Send actual message
- err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
- if err != nil {
- log.Printf("Error sending message to email: %v", err)
- return
+ m := gomail.NewMessage()
+ m.SetHeader("From", h.EmailSender)
+ m.SetHeader("To", emailAddress)
+ m.SetHeader("Subject", subject)
+ m.SetBody("text/plain", plainText)
+ m.AddAlternative("text/html", body.String())
+ sPort, _ := strconv.Atoi(h.EmailPort)
+ d := gomail.NewDialer(h.EmailHost, sPort, h.EmailSender, "")
+ d.TLSConfig = &tls.Config{InsecureSkipVerify: h.EmailInsecureSkipVerify}
+ if err := d.DialAndSend(m); err != nil {
+ fmt.Println(err)
+ panic(err)
}
- log.Println(fmt.Sprintf("Sent %s message to email", notification.Event))
+
+ // // Create authentication
+ // auth := smtp.PlainAuth("", sender, password, smtpHost)
+ // // Send actual message
+ // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, sender, to, body.Bytes())
+ // if err != nil {
+ // log.Printf("Error sending message to email: %v", err)
+ // return
+ // }
+ log.Println(fmt.Sprintf("Sent %s message to email", event))
}
func getEmailEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
index d430ff414d..cecb342057 100644
--- a/services/logs2notifications/internal/handler/main.go
+++ b/services/logs2notifications/internal/handler/main.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"log"
+ "regexp"
"time"
"github.com/cheshir/go-mq"
@@ -12,6 +13,7 @@ import (
"github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon"
lclient "github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/client"
"github.com/uselagoon/lagoon/services/logs2notifications/internal/lagoon/jwt"
+ "github.com/uselagoon/lagoon/services/logs2notifications/internal/schema"
)
// RabbitBroker .
@@ -57,6 +59,16 @@ type Messaging struct {
DisableEmail bool
DisableWebhooks bool
DisableS3 bool
+ EmailSender string
+ EmailSenderPassword string
+ EmailHost string
+ EmailPort string
+ EmailInsecureSkipVerify bool
+ S3FilesAccessKeyID string
+ S3FilesSecretAccessKey string
+ S3FilesBucket string
+ S3FilesRegion string
+ S3FilesOrigin string
}
// Notification .
@@ -91,6 +103,9 @@ type Notification struct {
Environment string `json:"environment"`
EnvironmentID string `json:"environmentId"`
EnvironmentName string `json:"environmentName"`
+ ServiceName string `json:"serviceName"`
+ Severity string `json:"severity"`
+ Description string `json:"description"`
Error string `json:"error"`
JobName string `json:"jobName"`
LogLink string `json:"logLink"`
@@ -106,7 +121,9 @@ type Notification struct {
RepoURL string `json:"repoUrl"`
Route string `json:"route"`
Routes []string `json:"routes"`
- Task string `json:"task"`
+ Task struct {
+ ID int `json:"id"`
+ } `json:"task"`
} `json:"meta"`
Message string `json:"message"`
}
@@ -119,7 +136,15 @@ type EventMap struct {
}
// NewMessaging returns a messaging with config
-func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, startupInterval int, enableDebug bool, appID string, disableSlack, disableRocketChat, disableMicrosoftTeams, disableEmail, disableWebhooks, disableS3 bool) *Messaging {
+func NewMessaging(config mq.Config,
+ lagoonAPI LagoonAPI,
+ startupAttempts int,
+ startupInterval int,
+ enableDebug bool,
+ appID string,
+ disableSlack, disableRocketChat, disableMicrosoftTeams, disableEmail, disableWebhooks, disableS3 bool,
+ emailSender, emailSenderPassword, emailHost, emailPort string, emailInsecureSkipVerify bool,
+ s3FilesAccessKeyID, s3FilesSecretAccessKey, s3FilesBucket, s3FilesRegion, s3FilesOrigin string) *Messaging {
return &Messaging{
Config: config,
LagoonAPI: lagoonAPI,
@@ -133,12 +158,21 @@ func NewMessaging(config mq.Config, lagoonAPI LagoonAPI, startupAttempts int, st
DisableEmail: disableEmail,
DisableWebhooks: disableWebhooks,
DisableS3: disableS3,
+ EmailSender: emailSender,
+ EmailSenderPassword: emailSenderPassword,
+ EmailHost: emailHost,
+ EmailPort: emailPort,
+ EmailInsecureSkipVerify: emailInsecureSkipVerify,
+ S3FilesAccessKeyID: s3FilesAccessKeyID,
+ S3FilesSecretAccessKey: s3FilesSecretAccessKey,
+ S3FilesBucket: s3FilesBucket,
+ S3FilesRegion: s3FilesRegion,
+ S3FilesOrigin: s3FilesOrigin,
}
}
// Consumer handles consuming messages sent to the queue that this action handler is connected to and processes them accordingly
func (h *Messaging) Consumer() {
- ctx := context.TODO()
var messageQueue mq.MQ
// if no mq is found when the goroutine starts, retry a few times before exiting
@@ -175,25 +209,38 @@ func (h *Messaging) Consumer() {
// Handle any tasks that go to the queue
log.Println("Listening for messages in queue lagoon-logs:notifications")
err = messageQueue.SetConsumerHandler("notifications-queue", func(message mq.Message) {
- notification := &Notification{}
- json.Unmarshal(message.Body(), notification)
- switch notification.Event {
- // check if this a `deployEnvironmentLatest` type of action
- // and perform the steps to run the mutation against the lagoon api
- case "api:unknownEvent":
- default:
+ h.processMessage(message.Body(), message.AppId())
+ message.Ack(false) // ack to remove from queue
+ })
+ if err != nil {
+ log.Println(fmt.Sprintf("Failed to set handler to consumer `%s`: %v", "items-queue", err))
+ }
+ <-forever
+}
+
+func (h *Messaging) processMessage(message []byte, applicationID string) {
+ ctx := context.Background()
+ notification := &Notification{}
+ json.Unmarshal(message, notification)
+
+ var buildLogs = regexp.MustCompile(`^build-logs:builddeploy-kubernetes:.*`)
+ var taskLogs = regexp.MustCompile(`^(build|task)-logs:job-kubernetes:.*`)
+ switch notification.Event {
+ case buildLogs.FindString(notification.Event):
+ // if this is a build logs message handle it accordingly
+ if !h.DisableS3 {
+ h.SendToS3(notification, buildMessageType)
+ }
+ case taskLogs.FindString(notification.Event):
+ // if this is a task logs message handle it accordingly
+ if !h.DisableS3 {
+ h.SendToS3(notification, taskMessageType)
+ }
+ default:
+ // all other events are notifications, so do notification handling with them
+ if notification.Project != "" {
// marshal unmarshal the data into the input we need to use when talking to the lagoon api
- token, err := jwt.OneMinuteAdminToken(h.LagoonAPI.TokenSigningKey, h.LagoonAPI.JWTAudience, h.LagoonAPI.JWTSubject, h.LagoonAPI.JWTIssuer)
- if err != nil {
- // the token wasn't generated
- if h.EnableDebug {
- log.Println(err)
- }
- break
- }
- // get all notifications for said project
- l := lclient.New(h.LagoonAPI.Endpoint, token, "logs2notifications", false)
- projectNotifications, err := lagoon.NotificationsForProject(ctx, notification.Project, l)
+ projectNotifications, err := h.getProjectNotifictions(ctx, notification.Project)
if err != nil {
log.Println(err)
break
@@ -201,49 +248,49 @@ func (h *Messaging) Consumer() {
if projectNotifications.Notifications != nil {
if len(projectNotifications.Notifications.Slack) > 0 && !h.DisableSlack {
for _, slack := range projectNotifications.Notifications.Slack {
- SendToSlack(notification, slack.Channel, slack.Webhook, message.AppId())
+ h.SendToSlack(notification, slack.Channel, slack.Webhook, applicationID)
}
}
if len(projectNotifications.Notifications.RocketChat) > 0 && !h.DisableRocketChat {
for _, rc := range projectNotifications.Notifications.RocketChat {
- SendToRocketChat(notification, rc.Channel, rc.Webhook, message.AppId())
+ h.SendToRocketChat(notification, rc.Channel, rc.Webhook, applicationID)
}
}
if len(projectNotifications.Notifications.Email) > 0 && !h.DisableEmail {
for _, email := range projectNotifications.Notifications.Email {
- SendToEmail(notification, email.EmailAddress, message.AppId())
+ h.SendToEmail(notification, email.EmailAddress)
}
}
if len(projectNotifications.Notifications.MicrosoftTeams) > 0 && !h.DisableMicrosoftTeams {
for _, teams := range projectNotifications.Notifications.MicrosoftTeams {
- SendToMicrosoftTeams(notification, teams.Webhook, message.AppId())
+ h.SendToMicrosoftTeams(notification, teams.Webhook)
}
}
- // if len(projectNotifications.Notifications.Webhook) > 0 {
- // fmt.Println(projectNotifications.Notifications.Webhook)
+ // if len(projectNotifications.Notifications.Webhook) > 0 && !h.DisableWebhooks {
+ // for _, hook := range projectNotifications.Notifications.Webhook {
+ // h.SendToWebhook(notification, hook.Webhook)
+ // }
// }
}
}
- message.Ack(false) // ack to remove from queue
- })
- if err != nil {
- log.Println(fmt.Sprintf("Failed to set handler to consumer `%s`: %v", "items-queue", err))
}
- <-forever
}
-// toLagoonLogs sends logs to the lagoon-logs message queue
-func (h *Messaging) toLagoonLogs(messageQueue mq.MQ, message map[string]interface{}) {
- msgBytes, err := json.Marshal(message)
+func (h *Messaging) getProjectNotifictions(ctx context.Context, projectName string) (*schema.Project, error) {
+ token, err := jwt.OneMinuteAdminToken(h.LagoonAPI.TokenSigningKey, h.LagoonAPI.JWTAudience, h.LagoonAPI.JWTSubject, h.LagoonAPI.JWTIssuer)
if err != nil {
+ // the token wasn't generated
if h.EnableDebug {
- log.Println(err, "Unable to encode message as JSON")
+ log.Println(err)
}
+ return nil, err
}
- producer, err := messageQueue.AsyncProducer("lagoon-logs")
+ // get all notifications for said project
+ l := lclient.New(h.LagoonAPI.Endpoint, token, "logs2notifications", false)
+ projectNotifications, err := lagoon.NotificationsForProject(ctx, projectName, l)
if err != nil {
- log.Println(fmt.Sprintf("Failed to get async producer: %v", err))
- return
+ log.Println(err)
+ return nil, err
}
- producer.Produce(msgBytes)
+ return projectNotifications, nil
}
diff --git a/services/logs2notifications/internal/handler/main_test.go b/services/logs2notifications/internal/handler/main_test.go
index 0adc367c3f..324e619d00 100644
--- a/services/logs2notifications/internal/handler/main_test.go
+++ b/services/logs2notifications/internal/handler/main_test.go
@@ -2,8 +2,12 @@ package handler
import (
"bytes"
+ "encoding/json"
+ "io/ioutil"
"reflect"
"testing"
+
+ "github.com/cheshir/go-mq"
)
func checkEqual(t *testing.T, got, want interface{}, msgs ...interface{}) {
@@ -15,4 +19,177 @@ func checkEqual(t *testing.T, got, want interface{}, msgs ...interface{}) {
}
t.Errorf(buf.String(), got, want)
}
-}
\ No newline at end of file
+}
+
+func TestProcessing(t *testing.T) {
+ config := mq.Config{}
+ graphQLConfig := LagoonAPI{
+ // Endpoint: svr.URL,
+ // TokenSigningKey: "jwtTokenSigningKey",
+ // JWTAudience: "jwtAudience",
+ // JWTSubject: "jwtSubject",
+ // JWTIssuer: "jwtIssuer",
+ }
+ messaging := NewMessaging(config,
+ graphQLConfig,
+ 1,
+ 1,
+ true,
+ "lagoonAppID",
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ "emailSender",
+ "emailSenderPassword",
+ "emailHost",
+ "emailPort",
+ true,
+ "s3FilesAccessKeyID",
+ "s3FilesSecretAccessKey",
+ "s3FilesBucket",
+ "s3FilesRegion",
+ "s3FilesOrigin",
+ )
+ var testCases = map[string]struct {
+ input string
+ description string
+ slack string
+ rocketchat string
+ emailhtml string
+ emailplain string
+ teams string
+ }{
+ "repoPushHandledGithub": {
+ description: "test github repo push handled events",
+ input: "testdata/input.repoPushHandledGithub.json",
+ slack: "testdata/repoPushHandled/slack.txt",
+ rocketchat: "testdata/repoPushHandled/rocketchat.txt",
+ emailhtml: "testdata/repoPushHandled/emailhtml.txt",
+ emailplain: "testdata/repoPushHandled/emailplain.txt",
+ teams: "testdata/repoPushHandled/teams.txt",
+ },
+ "repoPushSkippedGithub": {
+ description: "test github repo push skipped events",
+ input: "testdata/input.repoPushSkippedGithub.json",
+ slack: "testdata/repoPushSkipped/slack.txt",
+ rocketchat: "testdata/repoPushSkipped/rocketchat.txt",
+ emailhtml: "testdata/repoPushSkipped/emailhtml.txt",
+ emailplain: "testdata/repoPushSkipped/emailplain.txt",
+ teams: "testdata/repoPushSkipped/teams.txt",
+ },
+ "deleteEnvironmentGithub": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deleteEnvironmentGithub.json",
+ slack: "testdata/deleteEnvironment/slack.txt",
+ rocketchat: "testdata/deleteEnvironment/rocketchat.txt",
+ emailhtml: "testdata/deleteEnvironment/emailhtml.txt",
+ emailplain: "testdata/deleteEnvironment/emailplain.txt",
+ teams: "testdata/deleteEnvironment/teams.txt",
+ },
+ "deployEnvironment": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployEnvironment.json",
+ slack: "testdata/deployEnvironment/slack.txt",
+ rocketchat: "testdata/deployEnvironment/rocketchat.txt",
+ emailhtml: "testdata/deployEnvironment/emailhtml.txt",
+ emailplain: "testdata/deployEnvironment/emailplain.txt",
+ teams: "testdata/deployEnvironment/teams.txt",
+ },
+ "deployError": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployError.json",
+ slack: "testdata/deployError/slack.txt",
+ rocketchat: "testdata/deployError/rocketchat.txt",
+ emailhtml: "testdata/deployError/emailhtml.txt",
+ emailplain: "testdata/deployError/emailplain.txt",
+ teams: "testdata/deployError/teams.txt",
+ },
+ "deployFinished": {
+ description: "test github repo push deleted events",
+ input: "testdata/input.deployFinished.json",
+ slack: "testdata/deployFinished/slack.txt",
+ rocketchat: "testdata/deployFinished/rocketchat.txt",
+ emailhtml: "testdata/deployFinished/emailhtml.txt",
+ emailplain: "testdata/deployFinished/emailplain.txt",
+ teams: "testdata/deployFinished/teams.txt",
+ },
+ }
+ for name, tc := range testCases {
+ t.Run(name, func(tt *testing.T) {
+ // read the input into a the notification struct
+ inputBytes, err := ioutil.ReadFile(tc.input) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ notification := &Notification{}
+ json.Unmarshal(inputBytes, notification)
+
+ // process slack template
+ resultBytes, err := ioutil.ReadFile(tc.slack) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err := messaging.processSlackTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ t.Log(string(message))
+ tt.Fatalf("message doesn't match")
+ }
+
+ // process rocketchat template
+ resultBytes, err = ioutil.ReadFile(tc.rocketchat) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err = messaging.processRocketChatTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ t.Log(string(message))
+ tt.Fatalf("message doesn't match")
+ }
+
+ // process email templates
+ resultBytesHTML, err := ioutil.ReadFile(tc.emailhtml) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ resultBytesPlainText, err := ioutil.ReadFile(tc.emailplain) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, _, htmlMessage, plaintextMessage, err := messaging.processEmailTemplates(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if htmlMessage != string(resultBytesHTML) {
+ t.Log(string(htmlMessage))
+ tt.Fatalf("html message doesn't match")
+ }
+ if plaintextMessage != string(resultBytesPlainText) {
+ t.Log(string(plaintextMessage))
+ tt.Fatalf("plaintext message doesn't match")
+ }
+
+ // process teams template
+ resultBytes, err = ioutil.ReadFile(tc.teams) // just pass the file name
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ _, _, message, err = messaging.processMicrosoftTeamsTemplate(notification)
+ if err != nil {
+ tt.Fatalf("unexpected error %v", err)
+ }
+ if message != string(resultBytes) {
+ t.Log(string(message))
+ tt.Fatalf("message doesn't match")
+ }
+ })
+ }
+}
diff --git a/services/logs2notifications/internal/handler/microsoftteams_events.go b/services/logs2notifications/internal/handler/microsoftteams_events.go
index cc99b05331..87df357d1a 100644
--- a/services/logs2notifications/internal/handler/microsoftteams_events.go
+++ b/services/logs2notifications/internal/handler/microsoftteams_events.go
@@ -6,6 +6,8 @@ import (
"fmt"
"log"
"net/http"
+ "strings"
+ "text/template"
)
// MicrosoftTeamsData .
@@ -25,181 +27,101 @@ type MicrosoftTeamsSection struct {
}
// SendToMicrosoftTeams .
-func SendToMicrosoftTeams(notification *Notification, webhook, appID string) {
-
- emoji, color, template, err := getMicrosoftTeamsEvent(notification.Event)
+func (h *Messaging) SendToMicrosoftTeams(notification *Notification, webhook string) {
+ emoji, color, message, err := h.processMicrosoftTeamsTemplate(notification)
if err != nil {
return
}
+ h.sendMicrosoftTeamsMessage(emoji, color, webhook, notification.Event, notification.Meta.ProjectName, message)
+}
- var text string
- switch template {
+// processMicrosoftTeamsTemplate .
+func (h *Messaging) processMicrosoftTeamsTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getMicrosoftTeamsEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+
+ var teamsTpl string
+ switch tpl {
case "mergeRequestOpened":
- text = fmt.Sprintf("PR [#%s (%s)](%s) opened in [%s](%s)",
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
case "mergeRequestUpdated":
- text = fmt.Sprintf("PR [#%s (%s)](%s) updated in [%s](%s)",
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
case "mergeRequestClosed":
- text = fmt.Sprintf("PR [#%s (%s)](%s) closed in [%s](%s)",
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ teamsTpl = `PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
case "deleteEnvironment":
- text = fmt.Sprintf("Deleting environment `%s`",
- notification.Meta.EnvironmentName,
- )
+ teamsTpl = `Deleting environment ` + "`{{.EnvironmentName}}`"
case "repoPushHandled":
- text = fmt.Sprintf("[%s](%s/tree/%s)",
- notification.Meta.BranchName,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s ([%s](%s))",
- text,
- notification.Meta.ShortSha,
- notification.Meta.CommitURL,
- )
- }
- text = fmt.Sprintf("%s pushed in [%s](%s)",
- text,
- notification.Meta.RepoFullName,
- notification.Meta.RepoURL,
- )
+ teamsTpl = `[{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
case "repoPushSkipped":
- text = fmt.Sprintf("[%s](%s/tree/%s)",
- notification.Meta.BranchName,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s ([%s](%s))",
- text,
- notification.Meta.ShortSha,
- notification.Meta.CommitURL,
- )
- }
- text = fmt.Sprintf("%s pushed in [%s](%s) *deployment skipped*",
- text,
- notification.Meta.RepoFullName,
- notification.Meta.RepoURL,
- )
+ teamsTpl = `[{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
case "deployEnvironment":
- text = fmt.Sprintf("Deployment triggered `%s`",
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s (%s)",
- text,
- notification.Meta.ShortSha,
- )
- }
+ teamsTpl = `Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
case "removeFinished":
- text = fmt.Sprintf("Removed `%s`",
- notification.Meta.OpenshiftProject,
- )
+ teamsTpl = `Removed ` + "`{{.OpenshiftProject}}`" + ``
case "removeRetry":
- text = fmt.Sprintf("Removed `%s`",
- notification.Meta.OpenshiftProject,
- )
+ teamsTpl = `Removed ` + "`{{.OpenshiftProject}}`" + ``
case "notDeleted":
- text = fmt.Sprintf("`%s` not deleted. %s",
- notification.Meta.BranchName,
- notification.Meta.Error,
- )
+ teamsTpl = "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
case "deployError":
- if notification.Meta.ShortSha != "" {
- text += fmt.Sprintf("`%s` %s",
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- text += fmt.Sprintf(" `%s`",
- notification.Meta.BranchName,
- )
- }
- text += fmt.Sprintf(" Build `%s` Failed.",
- notification.Meta.BuildName,
- )
- if notification.Meta.LogLink != "" {
- text += fmt.Sprintf(" [Logs](%s) \r",
- notification.Meta.LogLink,
- )
- }
+ teamsTpl = "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
case "deployFinished":
- if notification.Meta.ShortSha != "" {
- text += fmt.Sprintf("`%s` %s",
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- text += fmt.Sprintf("`%s`",
- notification.Meta.BranchName,
- )
- }
- text += fmt.Sprintf(" Build `%s` Succeeded.",
- notification.Meta.BuildName,
- )
- if notification.Meta.LogLink != "" {
- text += fmt.Sprintf(" [Logs](%s) \r",
- notification.Meta.LogLink,
- )
- }
- text += fmt.Sprintf("* %s \n",
- notification.Meta.Route,
- )
- if len(notification.Meta.Routes) != 0 {
- for _, r := range notification.Meta.Routes {
- if r != notification.Meta.Route {
- text += fmt.Sprintf("* %s \n", r)
- }
- }
+ teamsTpl = "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
}
+ teamsTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
default:
- // do nothing
- return
+ return "", "", "", fmt.Errorf("no matching event")
}
- data := MicrosoftTeamsData{
+ var teamsMsg bytes.Buffer
+ t, _ := template.New("microsoftteams").Parse(teamsTpl)
+ t.Execute(&teamsMsg, notification.Meta)
+ return emoji, color, teamsMsg.String(), nil
+}
+
+func (h *Messaging) sendMicrosoftTeamsMessage(emoji, color, webhook, event, project, message string) {
+ teamsPayload := MicrosoftTeamsData{
Type: "MessageCard",
Context: "http://schema.org/extensions",
- Summary: text,
- Title: notification.Meta.ProjectName,
+ Summary: message,
+ Title: project,
ThemeColor: color,
Sections: []MicrosoftTeamsSection{
{
- ActivityText: text,
+ ActivityText: message,
ActivityImage: emoji,
},
},
}
- jsonBytes, _ := json.Marshal(data)
- req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(jsonBytes))
+ teamsPayloadBytes, _ := json.Marshal(teamsPayload)
+ req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(teamsPayloadBytes))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- log.Printf("Error sending message to rocketchat: %v", err)
+ log.Printf("Error sending message to microsoft teams: %v", err)
return
}
defer resp.Body.Close()
- log.Println(fmt.Sprintf("Sent %s message to rocketchat", notification.Event))
+ log.Println(fmt.Sprintf("Sent %s message to microsoft teams", event))
}
func getMicrosoftTeamsEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/rocketchat_events.go b/services/logs2notifications/internal/handler/rocketchat_events.go
index 1657a0d150..978d0c723e 100644
--- a/services/logs2notifications/internal/handler/rocketchat_events.go
+++ b/services/logs2notifications/internal/handler/rocketchat_events.go
@@ -6,6 +6,8 @@ import (
"fmt"
"log"
"net/http"
+ "strings"
+ "text/template"
)
// RocketChatData .
@@ -29,177 +31,79 @@ type RocketChatAttachmentField struct {
}
// SendToRocketChat .
-func SendToRocketChat(notification *Notification, channel, webhook, appID string) {
-
- emoji, color, template, err := getRocketChatEvent(notification.Event)
+func (h *Messaging) SendToRocketChat(notification *Notification, channel, webhook, appID string) {
+ emoji, color, message, err := h.processRocketChatTemplate(notification)
if err != nil {
return
}
+ h.sendRocketChatMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+}
+
+// SendToRocketChat .
+func (h *Messaging) processRocketChatTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getRocketChatEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
- var text string
- switch template {
+ var rcTpl string
+ switch tpl {
case "mergeRequestOpened":
- text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) opened in [%s](%s)",
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
case "mergeRequestUpdated":
- text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) updated in [%s](%s)",
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
case "mergeRequestClosed":
- text = fmt.Sprintf("*[%s]* PR [#%s (%s)](%s) closed in [%s](%s)",
- notification.Meta.ProjectName,
- notification.Meta.PullrequestNumber,
- notification.Meta.PullrequestTitle,
- notification.Meta.PullrequestURL,
- notification.Meta.RepoName,
- notification.Meta.RepoURL,
- )
+ rcTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
case "deleteEnvironment":
- text = fmt.Sprintf("*[%s]* Deleting environment `%s`",
- notification.Meta.ProjectName,
- notification.Meta.EnvironmentName,
- )
+ rcTpl = `*[{{.ProjectName}}]* Deleting environment ` + "`{{.EnvironmentName}}`"
case "repoPushHandled":
- text = fmt.Sprintf("*[%s]* [%s](%s/tree/%s)",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s ([%s](%s))",
- text,
- notification.Meta.ShortSha,
- notification.Meta.CommitURL,
- )
- }
- text = fmt.Sprintf("%s pushed in [%s](%s)",
- text,
- notification.Meta.RepoFullName,
- notification.Meta.RepoURL,
- )
+ rcTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
case "repoPushSkipped":
- text = fmt.Sprintf("*[%s]* [%s](%s/tree/%s)",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- notification.Meta.RepoURL,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s ([%s](%s))",
- text,
- notification.Meta.ShortSha,
- notification.Meta.CommitURL,
- )
- }
- text = fmt.Sprintf("%s pushed in [%s](%s) *deployment skipped*",
- text,
- notification.Meta.RepoFullName,
- notification.Meta.RepoURL,
- )
+ rcTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
case "deployEnvironment":
- text = fmt.Sprintf("*[%s]* Deployment triggered `%s`",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- )
- if notification.Meta.ShortSha != "" {
- text = fmt.Sprintf("%s (%s)",
- text,
- notification.Meta.ShortSha,
- )
- }
+ rcTpl = `*[{{.ProjectName}}]* Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
case "removeFinished":
- text = fmt.Sprintf("*[%s]* Removed `%s`",
- notification.Meta.ProjectName,
- notification.Meta.OpenshiftProject,
- )
+ rcTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
case "removeRetry":
- text = fmt.Sprintf("*[%s]* Removed `%s`",
- notification.Meta.ProjectName,
- notification.Meta.OpenshiftProject,
- )
+ rcTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
case "notDeleted":
- text = fmt.Sprintf("*[%s]* `%s` not deleted. %s",
- notification.Meta.ProjectName,
- notification.Meta.BranchName,
- notification.Meta.Error,
- )
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
case "deployError":
- text = fmt.Sprintf("*[%s]*",
- notification.Meta.ProjectName,
- )
- if notification.Meta.ShortSha != "" {
- text += fmt.Sprintf(" `%s` %s",
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- text += fmt.Sprintf(" `%s`",
- notification.Meta.BranchName,
- )
- }
- text += fmt.Sprintf(" Build `%s` Failed.",
- notification.Meta.BuildName,
- )
- if notification.Meta.LogLink != "" {
- text += fmt.Sprintf(" [Logs](%s) \r",
- notification.Meta.LogLink,
- )
- }
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}`
case "deployFinished":
- text = fmt.Sprintf("*[%s]*",
- notification.Meta.ProjectName,
- )
- if notification.Meta.ShortSha != "" {
- text += fmt.Sprintf(" `%s` %s",
- notification.Meta.BranchName,
- notification.Meta.ShortSha,
- )
- } else {
- text += fmt.Sprintf(" `%s`",
- notification.Meta.BranchName,
- )
- }
- text += fmt.Sprintf(" Build `%s` Succeeded.",
- notification.Meta.BuildName,
- )
- if notification.Meta.LogLink != "" {
- text += fmt.Sprintf(" [Logs](%s) \r",
- notification.Meta.LogLink,
- )
- }
- text += fmt.Sprintf("* %s \n",
- notification.Meta.Route,
- )
- if len(notification.Meta.Routes) != 0 {
- for _, r := range notification.Meta.Routes {
- if r != notification.Meta.Route {
- text += fmt.Sprintf("* %s \n", r)
- }
- }
+ rcTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} [Logs]({{.LogLink}}){{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
}
+ rcTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
default:
- // do nothing
- return
+ return "", "", "", fmt.Errorf("no matching event")
}
+ var rcMsg bytes.Buffer
+ t, _ := template.New("rocketchat").Parse(rcTpl)
+ t.Execute(&rcMsg, notification.Meta)
+ return emoji, color, rcMsg.String(), nil
+}
+func (h *Messaging) sendRocketChatMessage(emoji, color, appID, channel, webhook, event, message string) {
data := RocketChatData{
Channel: channel,
Attachments: []RocketChatAttachment{
{
- // Text: fmt.Sprintf("%s %s", emoji, notification.Message),
- Text: fmt.Sprintf("%s %s", emoji, text),
+ Text: fmt.Sprintf("%s %s", emoji, message),
Color: color,
Fields: []RocketChatAttachmentField{
{
@@ -211,7 +115,6 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
},
},
}
-
jsonBytes, _ := json.Marshal(data)
req, err := http.NewRequest("POST", webhook, bytes.NewBuffer(jsonBytes))
req.Header.Set("Content-Type", "application/json")
@@ -224,7 +127,7 @@ func SendToRocketChat(notification *Notification, channel, webhook, appID string
return
}
defer resp.Body.Close()
- log.Println(fmt.Sprintf("Sent %s message to rocketchat", notification.Event))
+ log.Println(fmt.Sprintf("Sent %s message to rocketchat", event))
}
func getRocketChatEvent(msgEvent string) (string, string, string, error) {
@@ -235,70 +138,70 @@ func getRocketChatEvent(msgEvent string) (string, string, string, error) {
}
var rocketChatEventTypeMap = map[string]EventMap{
- "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
- "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
-
- "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
- "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
-
- "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
-
- "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
- "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
- "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
- "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
-
- "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
- "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
- "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
-
- "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
- "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
- "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
-
- "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
- "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
-
- "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
- "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
- "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"},
- "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "deployFinished"}, //not in teams
-
- "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
- "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "lawngreen", Template: "removeFinished"},
-
- "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"}, //not in teams
- "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
-
- "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
- "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
- "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
- "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold", Template: "notDeleted"},
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "lawngreen", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "lawngreen", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "🛑", Color: "red", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "🛑", Color: "red", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold", Template: "notDeleted"},
// deprecated
- // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "gold"},
- // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "gold"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
// deprecated
- // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
- // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "red", Template: "deployError"},
+ // "task:deploy-openshift:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "🛑", Color: "red", Template: "deployError"},
// deprecated
- // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
- // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "gold", Template: "removeRetry"},
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "gold", Template: "removeRetry"},
}
diff --git a/services/logs2notifications/internal/handler/s3_events.go b/services/logs2notifications/internal/handler/s3_events.go
index f53ed93a5c..09600710de 100644
--- a/services/logs2notifications/internal/handler/s3_events.go
+++ b/services/logs2notifications/internal/handler/s3_events.go
@@ -3,65 +3,80 @@ package handler
import (
"bytes"
"fmt"
- "net/http"
- "os"
+ "log"
"github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
-var (
- AWS_S3_REGION = ""
- AWS_S3_BUCKET = ""
+// MessageType .
+type MessageType string
+
+const (
+ buildMessageType MessageType = "build"
+ taskMessageType MessageType = "task"
)
-func getS3Event(msgEvent string) (string, string, string, error) {
- if val, ok := s3Event[msgEvent]; ok {
- return val.Emoji, val.Color, val.Template, nil
- }
- return "", "", "", fmt.Errorf("no matching event source")
-}
+// SendToS3 .
+func (h *Messaging) SendToS3(notification *Notification, msgType MessageType) {
+ if msgType == buildMessageType {
+ h.uploadFileS3(
+ notification.Message,
+ fmt.Sprintf("buildlogs/%s/%s/%s-%s.txt",
+ notification.Project,
+ notification.Meta.BranchName,
+ notification.Meta.JobName,
+ notification.Meta.RemoteID,
+ ),
+ )
+ } else if msgType == taskMessageType {
+ filePath := fmt.Sprintf("tasklogs/%s/%d-%s.txt",
+ notification.Project,
+ notification.Meta.Task.ID,
+ notification.Meta.RemoteID,
+ )
+ if notification.Meta.Environment != "" {
+ filePath = fmt.Sprintf("tasklogs/%s/%s/%d-%s.txt",
+ notification.Project,
+ notification.Meta.Environment,
+ notification.Meta.Task.ID,
+ notification.Meta.RemoteID,
+ )
-var s3Event = map[string]EventMap{
- "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ }
+ h.uploadFileS3(
+ notification.Message,
+ filePath,
+ )
+ }
}
-// func main() {
-// session, err := session.NewSession(&aws.Config{Region: aws.String(AWS_S3_REGION)})
-// if err != nil {
-// log.Fatal(err)
-// }
-
-// // Upload Files
-// err = uploadFile(session, "test.png")
-// if err != nil {
-// log.Fatal(err)
-// }
-// }
-
-func uploadFile(session *session.Session, uploadFileDir string) error {
-
- upFile, err := os.Open(uploadFileDir)
+// UploadFileS3
+func (h *Messaging) uploadFileS3(message, fileName string) {
+ var forcePath bool
+ forcePath = true
+ session, err := session.NewSession(&aws.Config{
+ Region: aws.String(h.S3FilesRegion),
+ Endpoint: aws.String(h.S3FilesOrigin),
+ Credentials: credentials.NewStaticCredentials(h.S3FilesAccessKeyID, h.S3FilesSecretAccessKey, ""),
+ S3ForcePathStyle: &forcePath,
+ })
if err != nil {
- return err
+ log.Fatal(err)
}
- defer upFile.Close()
-
- upFileInfo, _ := upFile.Stat()
- var fileSize int64 = upFileInfo.Size()
- fileBuffer := make([]byte, fileSize)
- upFile.Read(fileBuffer)
_, err = s3.New(session).PutObject(&s3.PutObjectInput{
- Bucket: aws.String(AWS_S3_BUCKET),
- Key: aws.String(uploadFileDir),
- ACL: aws.String("private"),
- Body: bytes.NewReader(fileBuffer),
- ContentLength: aws.Int64(fileSize),
- ContentType: aws.String(http.DetectContentType(fileBuffer)),
- ContentDisposition: aws.String("attachment"),
- ServerSideEncryption: aws.String("AES256"),
+ Bucket: aws.String(h.S3FilesBucket),
+ Key: aws.String(fileName),
+ ACL: aws.String("private"),
+ Body: bytes.NewReader([]byte(message)),
+ ContentType: aws.String("text/plain"),
})
- return err
+ if err != nil {
+ log.Println(err)
+ }
+ log.Println(fmt.Sprintf("Uploaded file %s", fileName))
+ return
}
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
index 6d153e60a1..413fa75fb3 100644
--- a/services/logs2notifications/internal/handler/slack_events.go
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -1,21 +1,87 @@
package handler
import (
+ "bytes"
"fmt"
"log"
+ "strings"
+ "text/template"
"github.com/slack-go/slack"
)
// SendToSlack .
-func SendToSlack(notification *Notification, channel, webhook, appID string) {
-
- emoji, color, _, err := getSlackEvent(notification.Event)
+func (h *Messaging) SendToSlack(notification *Notification, channel, webhook, appID string) {
+ emoji, color, message, err := h.processSlackTemplate(notification)
if err != nil {
return
}
+ h.sendSlackMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+}
+
+// processSlackTemplate .
+func (h *Messaging) processSlackTemplate(notification *Notification) (string, string, string, error) {
+ emoji, color, tpl, err := getSlackEvent(notification.Event)
+ if err != nil {
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ if eventSplit[1] == "insert" {
+ tpl = "problemNotification"
+ }
+ }
+
+ var slackTpl string
+ switch tpl {
+ case "mergeRequestOpened":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) opened in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestUpdated":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) updated in [{{.RepoName}}]({{.RepoURL}})`
+ case "mergeRequestClosed":
+ slackTpl = `*[{{.ProjectName}}]* PR [#{{.PullrequestNumber}} ({{.PullrequestTitle}})]({{.PullrequestURL}}) closed in [{{.RepoName}}]({{.RepoURL}})`
+ case "deleteEnvironment":
+ slackTpl = `*[{{.ProjectName}}]* Deleting environment ` + "`{{.EnvironmentName}}`"
+ case "repoPushHandled":
+ slackTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}})`
+ case "repoPushSkipped":
+ slackTpl = `*[{{.ProjectName}}]* [{{.BranchName}}]({{.RepoURL}}/tree/{{.BranchName}}){{ if ne .ShortSha "" }} ([{{.ShortSha}}]({{.CommitURL}})){{end}} pushed in [{{.RepoFullName}}]({{.RepoURL}}) *deployment skipped*`
+ case "deployEnvironment":
+ slackTpl = `*[{{.ProjectName}}]* Deployment triggered ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}}`
+ case "removeFinished":
+ slackTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "removeRetry":
+ slackTpl = `*[{{.ProjectName}}]* Removed ` + "`{{.OpenshiftProject}}`" + ``
+ case "notDeleted":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + ` not deleted. {{.Error}}`
+ case "deployError":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}`
+ case "deployFinished":
+ slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}
+* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{end}}`
+ case "problemNotification":
+ eventSplit := strings.Split(notification.Event, ":")
+ if eventSplit[0] != "problem" && eventSplit[1] == "insert" {
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+ slackTpl = `*[{{.ProjectName}}]* New problem found for ` + "`{{.EnvironmentName}}`" + `
+* Service: ` + "`{{.ServiceName}}`" + `{{ if ne .Severity "" }}
+* Severity: {{.Severity}}{{end}}{{ if ne .Description "" }}
+* Description: {{.Description}}{{end}}`
+ default:
+ return "", "", "", fmt.Errorf("no matching event")
+ }
+
+ var slackMsg bytes.Buffer
+ t, _ := template.New("slack").Parse(slackTpl)
+ t.Execute(&slackMsg, notification.Meta)
+ return emoji, color, slackMsg.String(), nil
+}
+
+func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, event, message string) {
attachment := slack.Attachment{
- Text: fmt.Sprintf("%s %s", emoji, notification.Message),
+ Text: fmt.Sprintf("%s %s", emoji, message),
Color: color,
Footer: appID,
MarkdownIn: []string{"pretext", "text", "fields"},
@@ -25,13 +91,13 @@ func SendToSlack(notification *Notification, channel, webhook, appID string) {
Channel: channel,
}
- err = slack.PostWebhook(webhook, &postMsg)
+ err := slack.PostWebhook(webhook, &postMsg)
if err != nil {
// just log any errors
log.Printf("Error sending message to slack: %v", err)
return
}
- log.Println(fmt.Sprintf("Sent %s message to slack", notification.Event))
+ log.Println(fmt.Sprintf("Sent %s message to slack", event))
}
func getSlackEvent(msgEvent string) (string, string, string, error) {
@@ -42,70 +108,70 @@ func getSlackEvent(msgEvent string) (string, string, string, error) {
}
var slackEventTypeMap = map[string]EventMap{
- "github:pull_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "gitlab:merge_request:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"},
- "bitbucket:pullrequest:created:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
- "bitbucket:pullrequest:created:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
-
- "github:pull_request:synchronize:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "gitlab:merge_request:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
- "bitbucket:pullrequest:updated:opened:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
- "bitbucket:pullrequest:updated:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
-
- "github:pull_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "bitbucket:pullrequest:fulfilled:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "bitbucket:pullrequest:rejected:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
- "gitlab:merge_request:closed:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "mergeRequestClosed"},
-
- "github:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"},
- "gitlab:remove:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
- "bitbucket:delete:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
- "api:deleteEnvironment": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
-
- "github:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
- "bitbucket:repo:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
- "gitlab:push:handled": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushHandled"},
-
- "github:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
- "gitlab:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
- "bitbucket:push:skipped": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "repoPushSkipped"},
-
- "api:deployEnvironmentLatest": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
- "api:deployEnvironmentBranch": {Emoji: ":information_source:", Color: "#E8E8E8", Template: "deployEnvironment"},
-
- "task:deploy-openshift:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
- "task:remove-openshift-resources:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
- "task:builddeploy-openshift:complete": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"},
- "task:builddeploy-kubernetes:complete": {Emoji: ":white_check_mark:", Color: "good", Template: "deployFinished"}, //not in teams
-
- "task:remove-openshift:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "removeFinished"},
- "task:remove-kubernetes:finished": {Emoji: ":white_check_mark:", Color: "good", Template: "removeFinished"},
-
- "task:remove-openshift:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
- "task:remove-kubernetes:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
- "task:builddeploy-kubernetes:failed": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"}, //not in teams
- "task:builddeploy-openshift:failed": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
-
- "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
- "github:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
- "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
- "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning", Template: "notDeleted"},
+ "github:pull_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "gitlab:merge_request:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"},
+ "bitbucket:pullrequest:created:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in slack
+ "bitbucket:pullrequest:created:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestOpened"}, //not in teams
+
+ "github:pull_request:synchronize:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "gitlab:merge_request:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"},
+ "bitbucket:pullrequest:updated:opened:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in slack
+ "bitbucket:pullrequest:updated:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestUpdated"}, //not in teams
+
+ "github:pull_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:fulfilled:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "bitbucket:pullrequest:rejected:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+ "gitlab:merge_request:closed:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "mergeRequestClosed"},
+
+ "github:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"},
+ "gitlab:remove:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "bitbucket:delete:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in slack
+ "api:deleteEnvironment": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deleteEnvironment"}, //not in teams
+
+ "github:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "bitbucket:repo:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+ "gitlab:push:handled": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushHandled"},
+
+ "github:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "gitlab:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+ "bitbucket:push:skipped": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "repoPushSkipped"},
+
+ "api:deployEnvironmentLatest": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+ "api:deployEnvironmentBranch": {Emoji: "ℹ️", Color: "#E8E8E8", Template: "deployEnvironment"},
+
+ "task:deploy-openshift:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:remove-openshift-resources:finished": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-openshift:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"},
+ "task:builddeploy-kubernetes:complete": {Emoji: "✅", Color: "good", Template: "deployFinished"}, //not in teams
+
+ "task:remove-openshift:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+ "task:remove-kubernetes:finished": {Emoji: "✅", Color: "good", Template: "removeFinished"},
+
+ "task:remove-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:remove-kubernetes:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ "task:builddeploy-kubernetes:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"}, //not in teams
+ "task:builddeploy-openshift:failed": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+
+ "github:pull_request:closed:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "github:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "bitbucket:repo:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
+ "gitlab:push:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning", Template: "notDeleted"},
// deprecated
- // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: ":warning:", Color: "warning"},
- // "rest:deploy:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:remove:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:promote:receive": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:pullrequest:deploy": {Emoji: ":information_source:", Color: "#E8E8E8"},
- // "rest:pullrequest:remove": {Emoji: ":information_source:", Color: "#E8E8E8"},
+ // "rest:remove:CannotDeleteProductionEnvironment": {Emoji: "⚠️", Color: "warning"},
+ // "rest:deploy:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:remove:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:promote:receive": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:deploy": {Emoji: "ℹ️", Color: "#E8E8E8"},
+ // "rest:pullrequest:remove": {Emoji: "ℹ️", Color: "#E8E8E8"},
// deprecated
- // "task:deploy-openshift:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
- // "task:remove-openshift-resources:error": {Emoji: ":bangbang:", Color: "danger", Template: "deployError"},
+ // "task:deploy-openshift:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
+ // "task:remove-openshift-resources:error": {Emoji: "🛑", Color: "danger", Template: "deployError"},
// deprecated
- // "task:deploy-openshift:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
- // "task:remove-openshift:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
- // "task:remove-kubernetes:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
- // "task:remove-openshift-resources:retry": {Emoji: ":warning:", Color: "warning", Template: "removeRetry"},
+ // "task:deploy-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-kubernetes:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
+ // "task:remove-openshift-resources:retry": {Emoji: "⚠️", Color: "warning", Template: "removeRetry"},
}
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
new file mode 100644
index 0000000000..e4312f8960
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailhtml.txt
@@ -0,0 +1 @@
+Deleted environment
lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
new file mode 100644
index 0000000000..6afbbb9a49
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] deleted environment lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
new file mode 100644
index 0000000000..ae882c10ee
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
new file mode 100644
index 0000000000..ae882c10ee
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
new file mode 100644
index 0000000000..7236b3ac99
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deleteEnvironment/teams.txt
@@ -0,0 +1 @@
+Deleting environment `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
new file mode 100644
index 0000000000..2df8b891e1
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailhtml.txt
@@ -0,0 +1 @@
+Deployment triggered
lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
new file mode 100644
index 0000000000..a0d5c2f96a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] Deployment triggered on branch lagoon-type-override
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
new file mode 100644
index 0000000000..e38caf7ee5
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
new file mode 100644
index 0000000000..e38caf7ee5
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt b/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
new file mode 100644
index 0000000000..80e680018e
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployEnvironment/teams.txt
@@ -0,0 +1 @@
+Deployment triggered `lagoon-type-override`
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
new file mode 100644
index 0000000000..caa0842373
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/emailhtml.txt
@@ -0,0 +1,2 @@
+[ci-github-openshift]
lagoon-type-override
Build
lagoon-build-1234
error.
+
Logs
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
new file mode 100644
index 0000000000..6e842187b3
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/emailplain.txt
@@ -0,0 +1,2 @@
+[ci-github-openshift] lagoon-type-override Build lagoon-build-1234 error.
+ [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
new file mode 100644
index 0000000000..3188762d78
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Failed. [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/slack.txt b/services/logs2notifications/internal/handler/testdata/deployError/slack.txt
new file mode 100644
index 0000000000..cb1126de73
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Failed.
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployError/teams.txt b/services/logs2notifications/internal/handler/testdata/deployError/teams.txt
new file mode 100644
index 0000000000..ca2fa8daf9
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployError/teams.txt
@@ -0,0 +1 @@
+`lagoon-type-override` Build `lagoon-build-1234` Failed. [Logs](https://logs)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
new file mode 100644
index 0000000000..26155d23c4
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/emailhtml.txt
@@ -0,0 +1,10 @@
+[ci-github-openshift] lagoon-type-override
Build lagoon-build-1234
complete. Logs
+
+
+
+
+
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
new file mode 100644
index 0000000000..3364f45f8b
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/emailplain.txt
@@ -0,0 +1,4 @@
+[ci-github-openshift] lagoon-type-override Build lagoon-build-1234 complete. [Logs](https://logs)
+https://route1
+https://route2
+https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
new file mode 100644
index 0000000000..66f5f30f33
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/rocketchat.txt
@@ -0,0 +1,4 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Succeeded. [Logs](https://logs)
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
new file mode 100644
index 0000000000..c2040f005a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
@@ -0,0 +1,4 @@
+*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Succeeded.
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
new file mode 100644
index 0000000000..b68e460ad9
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/teams.txt
@@ -0,0 +1,4 @@
+`lagoon-type-override` Build `lagoon-build-1234` Succeeded. [Logs](https://logs)
+* https://route1
+* https://route2
+* https://route3
diff --git a/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json b/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
new file mode 100644
index 0000000000..798e855a54
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deleteEnvironmentGithub.json
@@ -0,0 +1,8 @@
+{
+ "project":"ci-github-openshift",
+ "event":"github:delete:handled",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override"
+ }
+ }
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json b/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
new file mode 100644
index 0000000000..e38a2e0442
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployEnvironment.json
@@ -0,0 +1,9 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentLatest",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "project":"ci-github-openshift"
+ }
+ }
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployError.json b/services/logs2notifications/internal/handler/testdata/input.deployError.json
new file mode 100644
index 0000000000..0f304888b4
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployError.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"task:builddeploy-kubernetes:failed",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "project":"ci-github-openshift",
+ "buildName":"lagoon-build-1234",
+ "logLink":"https://logs"
+ }
+ }
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.deployFinished.json b/services/logs2notifications/internal/handler/testdata/input.deployFinished.json
new file mode 100644
index 0000000000..be4eb952fd
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.deployFinished.json
@@ -0,0 +1,14 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"task:deploy-openshift:finished",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "buildName":"lagoon-build-1234",
+ "route":"https://route1",
+ "routes":["https://route1","https://route2","https://route3"],
+ "logLink":"https://logs"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json b/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
new file mode 100644
index 0000000000..ef4ca09114
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.repoPushHandledGithub.json
@@ -0,0 +1,12 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"github:push:handled",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "repoFullName":"owner/repository",
+ "repoURL":"github.com/owner/repository"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json b/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
new file mode 100644
index 0000000000..efcc4ccd5b
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/input.repoPushSkippedGithub.json
@@ -0,0 +1,12 @@
+{
+ "severity":"info",
+ "project":"ci-github-openshift",
+ "uuid":"",
+ "event":"github:push:skipped",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "branchName":"lagoon-type-override",
+ "repoFullName":"owner/repository",
+ "repoURL":"github.com/owner/repository"
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json b/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestClosed.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json b/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestOpened.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json b/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
new file mode 100644
index 0000000000..5d04ed7b12
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/mergeRequestUpdated.json
@@ -0,0 +1,11 @@
+{
+ "project":"ci-github-openshift",
+ "event":"api:deployEnvironmentBranch",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "pullrequestNumber":"",
+ "pullrequestTitle":"",
+ "repoURL":"",
+ "repoName": ""
+ }
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/notDeleted.json b/services/logs2notifications/internal/handler/testdata/notDeleted.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/services/logs2notifications/internal/handler/testdata/problemNotification.1.json b/services/logs2notifications/internal/handler/testdata/problemNotification.1.json
new file mode 100644
index 0000000000..21fc0fe8c4
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/problemNotification.1.json
@@ -0,0 +1,12 @@
+{
+ "project":"ci-github-openshift",
+ "event":"problem:insert:source_name:summary:severity",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override",
+ "serviceName":"nginx",
+ "severity":"danger",
+ "description":"this is bad"
+ },
+ "message":"sagsdgasdgsdg"
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/problemNotification.2.json b/services/logs2notifications/internal/handler/testdata/problemNotification.2.json
new file mode 100644
index 0000000000..918ba09e82
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/problemNotification.2.json
@@ -0,0 +1,12 @@
+{
+ "project":"ci-github-openshift",
+ "event":"problem:update:source_name:summary:severity",
+ "meta":{
+ "projectName":"ci-github-openshift",
+ "environmentName":"lagoon-type-override",
+ "serviceName":"nginx",
+ "severity":"danger",
+ "description":"this is bad"
+ },
+ "message":"sagsdgasdgsdg"
+}
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/removeFinished.json b/services/logs2notifications/internal/handler/testdata/removeFinished.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
new file mode 100644
index 0000000000..287e649a98
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailhtml.txt
@@ -0,0 +1 @@
+lagoon-type-override pushed in owner/repository
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
new file mode 100644
index 0000000000..93333fb083
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] lagoon-type-override pushed in owner/repository
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
new file mode 100644
index 0000000000..d68f558315
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
new file mode 100644
index 0000000000..d68f558315
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt b/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
new file mode 100644
index 0000000000..efc27fed55
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushHandled/teams.txt
@@ -0,0 +1 @@
+[lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository)
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
new file mode 100644
index 0000000000..d8bbb51bc0
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailhtml.txt
@@ -0,0 +1 @@
+lagoon-type-override pushed in owner/repository deployment skipped
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
new file mode 100644
index 0000000000..beaf598e56
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/emailplain.txt
@@ -0,0 +1 @@
+[ci-github-openshift] lagoon-type-override pushed in owner/repository *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
new file mode 100644
index 0000000000..9dc759260a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/rocketchat.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
new file mode 100644
index 0000000000..9dc759260a
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/slack.txt
@@ -0,0 +1 @@
+*[ci-github-openshift]* [lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
new file mode 100644
index 0000000000..ee43a45568
--- /dev/null
+++ b/services/logs2notifications/internal/handler/testdata/repoPushSkipped/teams.txt
@@ -0,0 +1 @@
+[lagoon-type-override](github.com/owner/repository/tree/lagoon-type-override) pushed in [owner/repository](github.com/owner/repository) *deployment skipped*
\ No newline at end of file
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
index 789201f56c..da2e918cb0 100644
--- a/services/logs2notifications/main.go
+++ b/services/logs2notifications/main.go
@@ -28,17 +28,25 @@ var (
jwtAudience string
jwtSubject string
jwtIssuer string
- s3FilesAccessKeyID string
- s3FilesSecretAccessKey string
- s3FilesBucket string
- s3FilesRegion string
- s3FilesOrigin string
- disableSlack bool
- disableRocketChat bool
- disableMicrosoftTeams bool
- disableEmail bool
- disableWebhooks bool
- disableS3 bool
+
+ s3FilesAccessKeyID string
+ s3FilesSecretAccessKey string
+ s3FilesBucket string
+ s3FilesRegion string
+ s3FilesOrigin string
+
+ disableSlack bool
+ disableRocketChat bool
+ disableMicrosoftTeams bool
+ disableEmail bool
+ disableWebhooks bool
+ disableS3 bool
+
+ emailSender string
+ emailSenderPassword string
+ emailHost string
+ emailPort string
+ emailInsecureSkipVerify bool
)
func main() {
@@ -70,28 +78,44 @@ func main() {
"The jwt audience.")
flag.StringVar(&jwtIssuer, "jwt-issuer", "logs2notifications",
"The jwt audience.")
- flag.StringVar(&s3FilesAccessKeyID, "s3-files-access-key", "minio",
- "The jwt audience.")
- flag.StringVar(&s3FilesSecretAccessKey, "s3-files-secret-access-key", "minio123",
- "The jwt audience.")
- flag.StringVar(&s3FilesBucket, "s3-files-bucket", "lagoon-files",
- "The jwt audience.")
- flag.StringVar(&s3FilesRegion, "s3-files-region", "",
- "The jwt audience.")
- flag.StringVar(&s3FilesOrigin, "s3-files-origin", "http://docker.for.mac.localhost:9000",
- "The jwt audience.")
+
+ // Other notifications configuration
flag.BoolVar(&disableSlack, "disable-slack", false,
"Disable the logs2slack feature.")
flag.BoolVar(&disableRocketChat, "disable-rocketchat", false,
"Disable the logs2rocketchat feature.")
flag.BoolVar(&disableMicrosoftTeams, "disable-microsoft-teams", false,
"Disable the logs2microsoftteams feature.")
- flag.BoolVar(&disableEmail, "disable-email", false,
- "Disable the logs2email feature.")
flag.BoolVar(&disableWebhooks, "disable-webhooks", false,
"Disable the logs2webhooks feature.")
+
+ // S3 configuration
flag.BoolVar(&disableS3, "disable-s3", false,
"Disable the logs2s3 feature.")
+ flag.StringVar(&s3FilesAccessKeyID, "s3-files-access-key", "minio",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesSecretAccessKey, "s3-files-secret-access-key", "minio123",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesBucket, "s3-files-bucket", "lagoon-files",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesRegion, "s3-files-region", "auto",
+ "The jwt audience.")
+ flag.StringVar(&s3FilesOrigin, "s3-files-origin", "http://minio.127.0.0.1.nip.io:9000",
+ "The jwt audience.")
+
+ // Email sending configuration
+ flag.BoolVar(&disableEmail, "disable-email", false,
+ "Disable the logs2email feature.")
+ flag.StringVar(&emailSender, "email-sender-address", "notifications@lagoon.sh",
+ "The email address to send notifications as.")
+ flag.StringVar(&emailSenderPassword, "email-sender-password", "",
+ "The password (if required) for the sending email address.")
+ flag.StringVar(&emailHost, "email-host", "localhost",
+ "The host name or address for the email server.")
+ flag.StringVar(&emailPort, "email-port", "1025",
+ "The port for the email server.")
+ flag.BoolVar(&emailInsecureSkipVerify, "email-tls-insecure-skip-verify", true,
+ "Use TLS verification when talking to the email server.")
flag.Parse()
// get overrides from environment variables
@@ -111,6 +135,11 @@ func main() {
s3FilesRegion = getEnv("S3_FILES_REGION", s3FilesRegion)
s3FilesOrigin = getEnv("S3_FILES_HOST", s3FilesOrigin)
+ emailSender = getEnv("EMAIL_SENDER_ADDRESS", emailSender)
+ emailSenderPassword = getEnv("EMAIL_SENDER_PASSWORD", emailSenderPassword)
+ emailHost = getEnv("EMAIL_HOST", emailHost)
+ emailPort = getEnv("EMAIL_PORT", emailPort)
+
enableDebug := true
// configure the backup handler settings
@@ -195,6 +224,16 @@ func main() {
disableEmail,
disableWebhooks,
disableS3,
+ emailSender,
+ emailSenderPassword,
+ emailHost,
+ emailPort,
+ emailInsecureSkipVerify,
+ s3FilesAccessKeyID,
+ s3FilesSecretAccessKey,
+ s3FilesBucket,
+ s3FilesRegion,
+ s3FilesOrigin,
)
// start the consumer
From 4f637d37ae7e9064a90dce3162f7cde34d1337f6 Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 8 Jun 2022 15:57:39 +1000
Subject: [PATCH 10/17] fix: pass the gcs value through to messaging
---
services/logs2notifications/internal/handler/main.go | 3 ++-
services/logs2notifications/main.go | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
index 23b19a432d..b0b0fe11d8 100644
--- a/services/logs2notifications/internal/handler/main.go
+++ b/services/logs2notifications/internal/handler/main.go
@@ -145,7 +145,7 @@ func NewMessaging(config mq.Config,
appID string,
disableSlack, disableRocketChat, disableMicrosoftTeams, disableEmail, disableWebhooks, disableS3 bool,
emailSender, emailSenderPassword, emailHost, emailPort string, emailInsecureSkipVerify bool,
- s3FilesAccessKeyID, s3FilesSecretAccessKey, s3FilesBucket, s3FilesRegion, s3FilesOrigin string) *Messaging {
+ s3FilesAccessKeyID, s3FilesSecretAccessKey, s3FilesBucket, s3FilesRegion, s3FilesOrigin string, s3isGCS bool) *Messaging {
return &Messaging{
Config: config,
LagoonAPI: lagoonAPI,
@@ -169,6 +169,7 @@ func NewMessaging(config mq.Config,
S3FilesBucket: s3FilesBucket,
S3FilesRegion: s3FilesRegion,
S3FilesOrigin: s3FilesOrigin,
+ S3IsGCS: s3isGCS,
}
}
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
index 39f84960ab..d596b6d231 100644
--- a/services/logs2notifications/main.go
+++ b/services/logs2notifications/main.go
@@ -238,6 +238,7 @@ func main() {
s3FilesBucket,
s3FilesRegion,
s3FilesOrigin,
+ s3isGCS,
)
// start the consumer
From 5bbe48abba4bfc88eab8576b3b8a3db3eb399f8d Mon Sep 17 00:00:00 2001
From: Ben Jackson
Date: Wed, 8 Jun 2022 16:10:28 +1000
Subject: [PATCH 11/17] test: fix missing bool
---
services/logs2notifications/internal/handler/main_test.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/services/logs2notifications/internal/handler/main_test.go b/services/logs2notifications/internal/handler/main_test.go
index 29687731ca..19a35ac43a 100644
--- a/services/logs2notifications/internal/handler/main_test.go
+++ b/services/logs2notifications/internal/handler/main_test.go
@@ -52,6 +52,7 @@ func TestProcessing(t *testing.T) {
"s3FilesBucket",
"s3FilesRegion",
"s3FilesOrigin",
+ false,
)
var testCases = map[string]struct {
input string
From d86524d11c05a9ab4c68dd4efd2b8310d9728f7b Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Fri, 5 Aug 2022 10:29:11 +1000
Subject: [PATCH 12/17] chore: yarn cleanup
---
yarn.lock | 131 +-----------------------------------------------------
1 file changed, 2 insertions(+), 129 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 9bc025f996..df91a7d349 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3911,33 +3911,6 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
-"@slack/client@^4.12.0":
- version "4.12.0"
- resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.12.0.tgz#ec14c84a572cd9afed398bbcf7241c5a83e94c57"
- integrity sha512-ltbdkcIWk2eIptCCT/oPmeCGlG8xb3kXfwuPTtvNujioLMo2xXqiPdfl7xK+AeUfnvj3fJLYbpTPuBTscuhgzw==
- dependencies:
- "@types/form-data" "^2.2.1"
- "@types/is-stream" "^1.1.0"
- "@types/node" ">=6.0.0"
- "@types/p-cancelable" "^1.0.0"
- "@types/p-queue" "^2.3.2"
- "@types/p-retry" "^3.0.0"
- "@types/retry" "^0.12.0"
- "@types/ws" "^5.1.1"
- axios "^0.18.0"
- eventemitter3 "^3.1.0"
- finity "^0.5.4"
- form-data "^2.3.3"
- is-stream "^1.1.0"
- object.entries "^1.1.0"
- object.getownpropertydescriptors "^2.0.3"
- object.values "^1.1.0"
- p-cancelable "~1.0.0"
- p-queue "^2.4.2"
- p-retry "^3.0.1"
- retry "^0.12.0"
- ws "^5.2.0"
-
"@storybook/addon-a11y@^5.3.0":
version "5.3.22"
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-5.3.22.tgz#3bf1414d7084812278a5fc0d48474430893a9440"
@@ -4853,11 +4826,6 @@
dependencies:
"@types/node" "*"
-"@types/events@*":
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
- integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
-
"@types/express-serve-static-core@*":
version "4.17.7"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz#dfe61f870eb549dc6d7e12050901847c7d7e915b"
@@ -4892,13 +4860,6 @@
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-4.1.8.tgz#95240361f1379addae0a005ced7c5c77b0b1e1d8"
integrity sha512-9ARxZRyXXCMMf8KjYhnD+GHE789HGwIqa1dolCRl/C6ExwvBIUmnIktLeY71TPmFOQiU3DQwrbQEX0pkyqX1tQ==
-"@types/form-data@^2.2.1":
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.5.0.tgz#5025f7433016f923348434c40006d9a797c1b0e8"
- integrity sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==
- dependencies:
- form-data "*"
-
"@types/fs-capacitor@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e"
@@ -4955,13 +4916,6 @@
resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83"
integrity sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==
-"@types/is-stream@^1.1.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1"
- integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==
- dependencies:
- "@types/node" "*"
-
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@@ -5068,7 +5022,7 @@
"@types/node" "*"
form-data "^3.0.0"
-"@types/node@*", "@types/node@>=6", "@types/node@>=6.0.0":
+"@types/node@*", "@types/node@>=6":
version "12.7.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.8.tgz#cb1bf6800238898bc2ff6ffa5702c3cadd350708"
integrity sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==
@@ -5098,25 +5052,6 @@
resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.2.tgz#d070fe6a6b78755d1092a3dc492d34c3d8f871c4"
integrity sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==
-"@types/p-cancelable@^1.0.0":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/@types/p-cancelable/-/p-cancelable-1.0.1.tgz#4f0ce8aa3ee0007c2768b9b3e6e22af20a6eecbd"
- integrity sha512-MGdhuVx7X2yJe4dgOnDQcZQAYgiC/QK1O5HUPgTMTxWYiOlyWEO5DWmPBlXQBU1F6/JM7aSgYBDrpt7kurC6dw==
- dependencies:
- p-cancelable "*"
-
-"@types/p-queue@^2.3.2":
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.2.tgz#16bc5fece69ef85efaf2bce8b13f3ebe39c5a1c8"
- integrity sha512-eKAv5Ql6k78dh3ULCsSBxX6bFNuGjTmof5Q/T6PiECDq0Yf8IIn46jCyp3RJvCi8owaEmm3DZH1PEImjBMd/vQ==
-
-"@types/p-retry@^3.0.0":
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/@types/p-retry/-/p-retry-3.0.1.tgz#44403f405b7b60d108df8ab37d8db81af1ea80f2"
- integrity sha512-LkZCWg4JxFdQR/nGNZcMiyKAbNG3DKBRS6nn6Hg4dLS82zxkdBJJcvf4zXFvDCEI+e4dZdQX6wreqs9RDGMRfw==
- dependencies:
- p-retry "*"
-
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -5195,11 +5130,6 @@
dependencies:
"@types/node" "*"
-"@types/retry@^0.12.0":
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
- integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
-
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@@ -5238,14 +5168,6 @@
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw==
-"@types/ws@^5.1.1":
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64"
- integrity sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==
- dependencies:
- "@types/events" "*"
- "@types/node" "*"
-
"@types/ws@^7.0.0":
version "7.2.5"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.5.tgz#513f28b04a1ea1aa9dc2cad3f26e8e37c88aae49"
@@ -10744,11 +10666,6 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-finity@^0.5.4:
- version "0.5.4"
- resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11"
- integrity sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==
-
flat-cache@^1.2.1:
version "1.3.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f"
@@ -10861,15 +10778,6 @@ fork-ts-checker-webpack-plugin@^4.1.0:
tapable "^1.0.0"
worker-rpc "^0.1.0"
-form-data@*, form-data@^2.3.3:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
- integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.6"
- mime-types "^2.1.12"
-
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
@@ -15535,11 +15443,6 @@ node-releases@^2.0.1:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5"
integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==
-nodemailer@^6.3.0:
- version "6.3.1"
- resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
- integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
-
nodemon@^1.12.1:
version "1.19.2"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.2.tgz#b0975147dc99b3761ceb595b3f9277084931dcc0"
@@ -15959,21 +15862,11 @@ osenv@^0.1.4:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
-p-cancelable@*:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
- integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
-
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
-p-cancelable@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.0.0.tgz#07e9c6d22c31f9c6784cb4f1e1454a79b6d9e2d6"
- integrity sha512-USgPoaC6tkTGlS831CxsVdmZmyb8tR1D+hStI84MyckLOzfJlYQUweomrwE3D8T7u5u5GVuW064LT501wHTYYA==
-
p-each-series@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71"
@@ -16057,31 +15950,11 @@ p-map@^3.0.0:
dependencies:
aggregate-error "^3.0.0"
-p-queue@^2.4.2:
- version "2.4.2"
- resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34"
- integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng==
-
p-reduce@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
-p-retry@*:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.1.0.tgz#9ce7cef2069e84bf590df3b8ec18d740109338d6"
- integrity sha512-oepllyG9gX1qH4Sm20YAKxg1GA7L7puhvGnTfimi31P07zSIj7SDV6YtuAx9nbJF51DES+2CIIRkXs8GKqWJxA==
- dependencies:
- "@types/retry" "^0.12.0"
- retry "^0.12.0"
-
-p-retry@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
- integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==
- dependencies:
- retry "^0.12.0"
-
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -18634,7 +18507,7 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
-retry@0.12.0, retry@^0.12.0:
+retry@0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
From c487094f5427eb499e1d7e4992a11cb73457a79e Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Thu, 25 Aug 2022 08:52:53 +1000
Subject: [PATCH 13/17] chore: fix slack deploy finished routes message and use
correct var names
---
.../logs2notifications/internal/handler/slack_events.go | 2 +-
.../internal/handler/testdata/deployFinished/slack.txt | 6 +++---
services/logs2notifications/main.go | 8 ++++----
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
index 413fa75fb3..c847a50e8a 100644
--- a/services/logs2notifications/internal/handler/slack_events.go
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -58,7 +58,7 @@ func (h *Messaging) processSlackTemplate(notification *Notification) (string, st
slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Failed. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}`
case "deployFinished":
slackTpl = `*[{{.ProjectName}}]* ` + "`{{.BranchName}}`" + `{{ if ne .ShortSha "" }} ({{.ShortSha}}){{end}} Build ` + "`{{.BuildName}}`" + ` Succeeded. {{if ne .LogLink ""}} <{{.LogLink}}|Logs>{{end}}
-* {{.Route}}{{range .Routes}}{{if ne . $.Route}}* {{.}}{{end}}
+{{.Route}}{{range .Routes}}{{if ne . $.Route}}{{.}}{{end}}
{{end}}`
case "problemNotification":
eventSplit := strings.Split(notification.Event, ":")
diff --git a/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
index c2040f005a..44fa5333ec 100644
--- a/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
+++ b/services/logs2notifications/internal/handler/testdata/deployFinished/slack.txt
@@ -1,4 +1,4 @@
*[ci-github-openshift]* `lagoon-type-override` Build `lagoon-build-1234` Succeeded.
-* https://route1
-* https://route2
-* https://route3
+https://route1
+https://route2
+https://route3
diff --git a/services/logs2notifications/main.go b/services/logs2notifications/main.go
index d596b6d231..6721dde53c 100644
--- a/services/logs2notifications/main.go
+++ b/services/logs2notifications/main.go
@@ -69,7 +69,7 @@ func main() {
"The number of startup attempts before exiting.")
flag.IntVar(&startupConnectionInterval, "startup-connection-interval-seconds", 30,
"The duration between startup attempts.")
- flag.StringVar(&lagoonAPIHost, "lagoon-api-host", "http://localhost:3000/graphql",
+ flag.StringVar(&lagoonAPIHost, "lagoon-api-host", "http://localhost:3000",
"The host for the lagoon api.")
flag.StringVar(&jwtTokenSigningKey, "jwt-token-signing-key", "super-secret-string",
"The jwt signing token key or secret.")
@@ -124,10 +124,10 @@ func main() {
// get overrides from environment variables
mqUser = getEnv("RABBITMQ_USERNAME", mqUser)
mqPass = getEnv("RABBITMQ_PASSWORD", mqPass)
- mqHost = getEnv("RABBITMQ_ADDRESS", mqHost)
+ mqHost = getEnv("RABBITMQ_HOST", mqHost)
mqPort = getEnv("RABBITMQ_PORT", mqPort)
lagoonAPIHost = getEnv("GRAPHQL_ENDPOINT", lagoonAPIHost)
- jwtTokenSigningKey = getEnv("JWT_SECRET", jwtTokenSigningKey)
+ jwtTokenSigningKey = getEnv("JWTSECRET", jwtTokenSigningKey)
jwtAudience = getEnv("JWT_AUDIENCE", jwtAudience)
jwtSubject = getEnv("JWT_SUBJECT", jwtSubject)
jwtIssuer = getEnv("JWT_ISSUER", jwtIssuer)
@@ -153,7 +153,7 @@ func main() {
Password: mqPass,
}
graphQLConfig := handler.LagoonAPI{
- Endpoint: lagoonAPIHost,
+ Endpoint: fmt.Sprintf("%s/graphql", lagoonAPIHost),
TokenSigningKey: jwtTokenSigningKey,
JWTAudience: jwtAudience,
JWTSubject: jwtSubject,
From fcaec42d4fdda7d90d6a4e6d7d9df04d1a5fda1b Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Thu, 25 Aug 2022 09:28:20 +1000
Subject: [PATCH 14/17] chore: add some more information for messaging in logs
---
.../internal/handler/email_events.go | 2 +-
services/logs2notifications/internal/handler/main.go | 2 +-
.../internal/handler/microsoftteams_events.go | 4 ++--
.../internal/handler/rocketchat_events.go | 8 ++++----
.../internal/handler/slack_events.go | 7 ++++---
.../internal/handler/webhook_events.go | 10 ++++++++--
6 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/services/logs2notifications/internal/handler/email_events.go b/services/logs2notifications/internal/handler/email_events.go
index 8004ddb21d..5a229e83e7 100644
--- a/services/logs2notifications/internal/handler/email_events.go
+++ b/services/logs2notifications/internal/handler/email_events.go
@@ -204,7 +204,7 @@ func (h *Messaging) sendEmailMessage(emoji, color, subject, event, project, emai
// log.Printf("Error sending message to email: %v", err)
// return
// }
- log.Println(fmt.Sprintf("Sent %s message to email", event))
+ log.Println(fmt.Sprintf("Sent %s message to email for project %s", event, project))
}
func getEmailEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/main.go b/services/logs2notifications/internal/handler/main.go
index b0b0fe11d8..1852a029d5 100644
--- a/services/logs2notifications/internal/handler/main.go
+++ b/services/logs2notifications/internal/handler/main.go
@@ -123,7 +123,7 @@ type Notification struct {
Route string `json:"route"`
Routes []string `json:"routes"`
Task struct {
- ID int `json:"id"`
+ ID string `json:"id"`
} `json:"task"`
} `json:"meta"`
Message string `json:"message"`
diff --git a/services/logs2notifications/internal/handler/microsoftteams_events.go b/services/logs2notifications/internal/handler/microsoftteams_events.go
index 87df357d1a..4335c16e8c 100644
--- a/services/logs2notifications/internal/handler/microsoftteams_events.go
+++ b/services/logs2notifications/internal/handler/microsoftteams_events.go
@@ -117,11 +117,11 @@ func (h *Messaging) sendMicrosoftTeamsMessage(emoji, color, webhook, event, proj
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- log.Printf("Error sending message to microsoft teams: %v", err)
+ log.Printf("Error sending message to microsoft teams for project %s: %v", project, err)
return
}
defer resp.Body.Close()
- log.Println(fmt.Sprintf("Sent %s message to microsoft teams", event))
+ log.Println(fmt.Sprintf("Sent %s message to microsoft teams for project %s", event, project))
}
func getMicrosoftTeamsEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/rocketchat_events.go b/services/logs2notifications/internal/handler/rocketchat_events.go
index 978d0c723e..9e08ccc7a6 100644
--- a/services/logs2notifications/internal/handler/rocketchat_events.go
+++ b/services/logs2notifications/internal/handler/rocketchat_events.go
@@ -36,7 +36,7 @@ func (h *Messaging) SendToRocketChat(notification *Notification, channel, webhoo
if err != nil {
return
}
- h.sendRocketChatMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+ h.sendRocketChatMessage(emoji, color, appID, channel, webhook, notification.Event, notification.Meta.ProjectName, message)
}
// SendToRocketChat .
@@ -98,7 +98,7 @@ func (h *Messaging) processRocketChatTemplate(notification *Notification) (strin
return emoji, color, rcMsg.String(), nil
}
-func (h *Messaging) sendRocketChatMessage(emoji, color, appID, channel, webhook, event, message string) {
+func (h *Messaging) sendRocketChatMessage(emoji, color, appID, channel, webhook, event, project, message string) {
data := RocketChatData{
Channel: channel,
Attachments: []RocketChatAttachment{
@@ -123,11 +123,11 @@ func (h *Messaging) sendRocketChatMessage(emoji, color, appID, channel, webhook,
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
- log.Printf("Error sending message to rocketchat: %v", err)
+ log.Printf("Error sending message to rocketchat channel %s for project %s: %v", channel, project, err)
return
}
defer resp.Body.Close()
- log.Println(fmt.Sprintf("Sent %s message to rocketchat", event))
+ log.Println(fmt.Sprintf("Sent %s message to rocketchat channel %s for project %s", event, channel, project))
}
func getRocketChatEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
index c847a50e8a..b3d6462742 100644
--- a/services/logs2notifications/internal/handler/slack_events.go
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -16,7 +16,7 @@ func (h *Messaging) SendToSlack(notification *Notification, channel, webhook, ap
if err != nil {
return
}
- h.sendSlackMessage(emoji, color, appID, channel, webhook, notification.Event, message)
+ h.sendSlackMessage(emoji, color, appID, channel, webhook, notification.Event, notification.Meta.ProjectName, message)
}
// processSlackTemplate .
@@ -94,10 +94,11 @@ func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, even
err := slack.PostWebhook(webhook, &postMsg)
if err != nil {
// just log any errors
- log.Printf("Error sending message to slack: %v", err)
+ log.Printf("Error sending message to slack channel %s for project %s: %v", channel, project, err)
return
}
- log.Println(fmt.Sprintf("Sent %s message to slack", event))
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to slack channel %s for project %s", event, channel, project))
}
func getSlackEvent(msgEvent string) (string, string, string, error) {
diff --git a/services/logs2notifications/internal/handler/webhook_events.go b/services/logs2notifications/internal/handler/webhook_events.go
index 6e25acf3d1..6915809509 100644
--- a/services/logs2notifications/internal/handler/webhook_events.go
+++ b/services/logs2notifications/internal/handler/webhook_events.go
@@ -25,10 +25,10 @@ func (h *Messaging) SendToWebhook(notification *Notification, webhook schema.Not
if err != nil {
return
}
- h.sendWebhookMessage(*message, webhook)
+ h.sendWebhookMessage(notification.Meta.ProjectName, *message, webhook)
}
-func (h *Messaging) sendWebhookMessage(data WebhookData, webhook schema.NotificationWebhook) {
+func (h *Messaging) sendWebhookMessage(project, data WebhookData, webhook schema.NotificationWebhook) {
message, _ := json.Marshal(data)
req, err := http.NewRequest("POST", webhook.Webhook, bytes.NewBuffer(message))
req.Header.Set("Content-Type", "application/json")
@@ -42,6 +42,12 @@ func (h *Messaging) sendWebhookMessage(data WebhookData, webhook schema.Notifica
}
defer resp.Body.Close()
log.Println(fmt.Sprintf("Sent %s message to webhook", data.Event))
+ if err != nil {
+ log.Printf("Error sending message to webhook for project %s: %v", project, err)
+ return
+ }
+ defer resp.Body.Close()
+ log.Println(fmt.Sprintf("Sent %s message to webhook for project %s", data.Event, project))
}
// processWebhookTemplate .
From 40bc60fb3d58c3f6b1f5f00184f5e8f654ccd20c Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Thu, 25 Aug 2022 09:34:37 +1000
Subject: [PATCH 15/17] fix: missing arguments
---
services/logs2notifications/internal/handler/s3_events.go | 4 ++--
services/logs2notifications/internal/handler/slack_events.go | 3 +--
.../logs2notifications/internal/handler/webhook_events.go | 2 +-
3 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/services/logs2notifications/internal/handler/s3_events.go b/services/logs2notifications/internal/handler/s3_events.go
index a8082ec005..ed0132e66c 100644
--- a/services/logs2notifications/internal/handler/s3_events.go
+++ b/services/logs2notifications/internal/handler/s3_events.go
@@ -33,13 +33,13 @@ func (h *Messaging) SendToS3(notification *Notification, msgType MessageType) {
),
)
} else if msgType == taskMessageType {
- filePath := fmt.Sprintf("tasklogs/%s/%d-%s.txt",
+ filePath := fmt.Sprintf("tasklogs/%s/%s-%s.txt",
notification.Project,
notification.Meta.Task.ID,
notification.Meta.RemoteID,
)
if notification.Meta.Environment != "" {
- filePath = fmt.Sprintf("tasklogs/%s/%s/%d-%s.txt",
+ filePath = fmt.Sprintf("tasklogs/%s/%s/%s-%s.txt",
notification.Project,
helpers.ShortenEnvironment(notification.Project, helpers.MakeSafe(notification.Meta.Environment)),
notification.Meta.Task.ID,
diff --git a/services/logs2notifications/internal/handler/slack_events.go b/services/logs2notifications/internal/handler/slack_events.go
index b3d6462742..5134444975 100644
--- a/services/logs2notifications/internal/handler/slack_events.go
+++ b/services/logs2notifications/internal/handler/slack_events.go
@@ -79,7 +79,7 @@ func (h *Messaging) processSlackTemplate(notification *Notification) (string, st
return emoji, color, slackMsg.String(), nil
}
-func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, event, message string) {
+func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, event, project, message string) {
attachment := slack.Attachment{
Text: fmt.Sprintf("%s %s", emoji, message),
Color: color,
@@ -97,7 +97,6 @@ func (h *Messaging) sendSlackMessage(emoji, color, appID, channel, webhook, even
log.Printf("Error sending message to slack channel %s for project %s: %v", channel, project, err)
return
}
- defer resp.Body.Close()
log.Println(fmt.Sprintf("Sent %s message to slack channel %s for project %s", event, channel, project))
}
diff --git a/services/logs2notifications/internal/handler/webhook_events.go b/services/logs2notifications/internal/handler/webhook_events.go
index 6915809509..f6e76dff8c 100644
--- a/services/logs2notifications/internal/handler/webhook_events.go
+++ b/services/logs2notifications/internal/handler/webhook_events.go
@@ -28,7 +28,7 @@ func (h *Messaging) SendToWebhook(notification *Notification, webhook schema.Not
h.sendWebhookMessage(notification.Meta.ProjectName, *message, webhook)
}
-func (h *Messaging) sendWebhookMessage(project, data WebhookData, webhook schema.NotificationWebhook) {
+func (h *Messaging) sendWebhookMessage(project string, data WebhookData, webhook schema.NotificationWebhook) {
message, _ := json.Marshal(data)
req, err := http.NewRequest("POST", webhook.Webhook, bytes.NewBuffer(message))
req.Header.Set("Content-Type", "application/json")
From 58d72d7edcc380223763f7590a2fd739b0a1ff7b Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Fri, 26 Aug 2022 10:39:05 +1000
Subject: [PATCH 16/17] chore: bump to go 1.18
---
services/logs2notifications/Dockerfile | 7 +++----
services/logs2notifications/go.mod | 21 ++++++++++++++++-----
2 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/services/logs2notifications/Dockerfile b/services/logs2notifications/Dockerfile
index 49ee341fbb..770253a651 100644
--- a/services/logs2notifications/Dockerfile
+++ b/services/logs2notifications/Dockerfile
@@ -1,8 +1,7 @@
# build the binary
-ARG GO_VERSION
ARG UPSTREAM_REPO
ARG UPSTREAM_TAG
-FROM golang:${GO_VERSION:-1.16.4} AS builder
+FROM golang:1.18-alpine3.16 AS builder
# bring in all the packages
COPY . /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
WORKDIR /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
@@ -26,10 +25,10 @@ COPY --from=builder /go/src/github.com/uselagoon/lagoon/services/logs2notificati
ENV LAGOON=logs2notifications
# set defaults
-ENV JWT_SECRET=super-secret-string \
+ENV JWTSECRET=super-secret-string \
JWT_AUDIENCE=api.dev \
GRAPHQL_ENDPOINT="http://api:3000/graphql" \
- RABBITMQ_ADDRESS=broker \
+ RABBITMQ_HOST=broker \
RABBITMQ_PORT=5672 \
RABBITMQ_USERNAME=guest \
RABBITMQ_PASSWORD=guest
diff --git a/services/logs2notifications/go.mod b/services/logs2notifications/go.mod
index df048ac4c4..270f8259f1 100644
--- a/services/logs2notifications/go.mod
+++ b/services/logs2notifications/go.mod
@@ -1,20 +1,31 @@
module github.com/uselagoon/lagoon/services/logs2notifications
-go 1.16
+go 1.18
require (
github.com/aws/aws-sdk-go v1.41.11
- github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
github.com/cheshir/go-mq v1.0.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/fsouza/go-dockerclient v1.7.3 // indirect
github.com/machinebox/graphql v0.2.3-0.20181106130121-3a9253180225
- github.com/matryer/is v1.4.0 // indirect
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2
github.com/slack-go/slack v0.9.5
+ gopkg.in/mail.v2 v2.3.1
+)
+
+require (
+ github.com/NeowayLabs/wabbit v0.0.0-20200409220312-12e68ab5b0c6 // indirect
+ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
+ github.com/fsouza/go-dockerclient v1.7.3 // indirect
+ github.com/google/uuid v1.1.1 // indirect
+ github.com/gorilla/websocket v1.4.2 // indirect
+ github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/matryer/is v1.4.0 // indirect
+ github.com/pborman/uuid v1.2.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71 // indirect
+ github.com/stretchr/testify v1.4.0 // indirect
github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
- gopkg.in/mail.v2 v2.3.1
)
// Fixes for AppID
From aacd0df245d6c68e234f24504063ed82dbe2b049 Mon Sep 17 00:00:00 2001
From: shreddedbacon
Date: Fri, 26 Aug 2022 10:41:38 +1000
Subject: [PATCH 17/17] chore: remove test from dockerfile
---
services/logs2notifications/Dockerfile | 2 --
1 file changed, 2 deletions(-)
diff --git a/services/logs2notifications/Dockerfile b/services/logs2notifications/Dockerfile
index 770253a651..72d59bb61d 100644
--- a/services/logs2notifications/Dockerfile
+++ b/services/logs2notifications/Dockerfile
@@ -6,8 +6,6 @@ FROM golang:1.18-alpine3.16 AS builder
COPY . /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
WORKDIR /go/src/github.com/uselagoon/lagoon/services/logs2notifications/
-# tests currently don't work because mocking rabbit is interesting
-RUN GO111MODULE=on go test ./...
# compile
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o logs2notifications .