Skip to content

Commit 0268cd9

Browse files
Merge pull request #19 from opensource254/dev
Notifications
2 parents 59d1895 + 1923ee8 commit 0268cd9

9 files changed

Lines changed: 319 additions & 6 deletions

File tree

.env.example

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
PORT=3232
22
SECRET_TOKEM=
3-
ALLOWED_HOSTS="github.com,bitbucket.org,gitlab.com"
3+
SLACK_WEBHOOK_URL=
4+
5+
SMTP_HOST=smtp.mailtrap.io
6+
SMTP_PORT=2525
7+
SMTP_USERNAME=
8+
SMTP_PASSWORD=
9+
10+
NOTIFICATION_EMAIL=
11+
NOTIFICATION_FROM=
12+
13+
ALLOW_NOTIFiCATIONS=true
14+
SUCCESS_NOTIFICATIONS=false
15+
ERROR_NOTIFICATIONS=false

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,40 @@ SECRET=<your-secret>
5353
Add this secret to your webhook secret too.
5454
* If a security check fails, the endpoint will respond with error 403
5555

56+
## Notifications
57+
Two notifications channels are currently supported.
58+
* Email (The current supported protocal is SMTP)
59+
* Slack
60+
Turning notifications on
61+
```env
62+
ALLOW_NOTIFICATIONS=true
63+
// Notify on successful deployment
64+
SUCCESS_NOTIFICATIONS=false
65+
// Notify on failures/warnings
66+
ERROR_NOTIFICATIONS=false
67+
```
68+
69+
### Email Configuration
70+
Add the config details in the .env file.
71+
example
72+
```env
73+
SMTP_HOST=smtp.mailtrap.io
74+
SMTP_PORT=2525
75+
SMTP_USERNAME=
76+
SMTP_PASSWORD=
77+
78+
// The email to send notifications to
79+
NOTIFICATION_EMAIL=
80+
// The from address that will be shown in the email
81+
NOTIFICATION_FROM=
82+
```
83+
### Slack Configuration
84+
Slack config is as simple as adding the webhook config.
85+
Create a Slack app and add the webhook url for the app to `.env` like below
86+
```env
87+
SLACK_WEBHOOK_URL=
88+
```
89+
5690
## Debugging
5791
Configuration erros are logged in the error.log file. This file is not version controlled.
5892
```log
@@ -65,8 +99,8 @@ Sat, 13 Jun 2020 10:01:58 GMT Config: your-awesome-config, Error: /bin/sh: 1: ks
6599
- [x] Basic functionality
66100
- [x] Refactor
67101
- [x] Webhook Security
102+
- [x] Notifications
68103
- [ ] Web interface
69-
- [ ] Notifications
70104

71105
## Contributing
72106
Please visit our [guidelines](https://opensource254.github.io/guidelines)

config/mail.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const mail = {
2+
/**The email host */
3+
SMTP_HOST: process.env.SMTP_HOST || 'smtp.mailtrap.io',
4+
SMTP_PORT: process.env.SMTP_PORT || 25,
5+
SMTP_USERNAME: process.env.SMTP_USERNAME || '',
6+
SMTP_PASSWORD: process.env.SMTP_PASSWORD || ''
7+
}
8+
9+
module.exports = Object.freeze(mail)

config/notifications.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* -------------------------------------------------------------------
3+
* This is where th notification config lives.
4+
* All these values can be overwritten by the environment variables
5+
* -------------------------------------------------------------------
6+
*/
7+
const notificationConfig = {
8+
/**This is used to determine if notifications are enabled */
9+
allowNotifications: process.env.ALLOW_NOTIFICATIONS || false,
10+
11+
12+
/**
13+
* Your Slack webhook URL. If empty Slack notifications will be off
14+
* This is the URL you received when creating a Slack APP
15+
* Slack is ignored if this is null or false
16+
*
17+
*/
18+
slackWebhookURL: process.env.SLACK_WEBHOOK_URL,
19+
20+
21+
/**
22+
* Notification Email
23+
* An email will be send to this email(s) according to
24+
* Your config
25+
* */
26+
notificationEmail: process.env.NOTIFICATION_EMAIL,
27+
28+
/**
29+
* Notification from email
30+
* The From adress that will be shown in the email
31+
*/
32+
notificationFrom: process.env.NOTIFICATION_FROM,
33+
34+
35+
/**
36+
* Get notifications on errors that occour
37+
* during deployment
38+
*/
39+
notifyOnErrors: process.env.ERROR_NOTIFICATIONS || true,
40+
41+
/**
42+
*
43+
* This is used to set notification on successful deployments
44+
*/
45+
notifyOnSucess: process.env.SUCCESS_NOTIFICATIONS || true
46+
47+
/**
48+
* Telegram
49+
* Coming Soon
50+
*/
51+
// TODO Add telegram
52+
}
53+
54+
module.exports = Object.freeze(notificationConfig)

package-lock.json

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"debug": "~2.6.9",
1212
"dotenv": "^8.2.0",
1313
"express": "~4.16.1",
14-
"morgan": "~1.9.1"
14+
"morgan": "~1.9.1",
15+
"nodemailer": "^6.4.10"
1516
},
1617
"devDependencies": {
1718
"nodemon": "^2.0.4"

services/deployment.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,57 @@
11
const fs = require('fs')
22
const { exec } = require('child_process')
3+
const NotificationService = require('./notifications')
34

45
/**
56
* Handle deployment
67
* @param {Array} Config
78
*/
89
const deployment = (Config = []) => {
910
exec(`${Config.command}`, (_err, stdout, stderr) => {
11+
/**The output logged during the command excecution */
1012
if (stdout) {
11-
// TODO Make this available to the user
13+
const slack = (`
14+
*Deployment for _${Config.name}_ was successful 😃* \n*Output*: \`\`\`${stdout.trim()}\`\`\`
15+
`)
16+
17+
const mail = {
18+
subject: `Deployment for ${Config.name} was successfull ✅`,
19+
data: (`
20+
<h3>Deployment for <i>${Config.name}</i> was successful 😃</h3> <br/>
21+
<b>Output:</b> <code color="green">${stdout.trim()}</code>
22+
`)
23+
}
24+
new NotificationService({ slack, mail }, 'success').sendNotifications()
1225
}
26+
/** If an error occoured */
1327
if (stderr) {
14-
fs.writeFileSync('error.log', `${new Date().toUTCString()}, Config: ${Config.name}, Error: ${stderr}`, {
28+
const slack = (`
29+
*Deployment for * _${Config.name}_ failed 😢 \n*Date* \`${new Date().toUTCString()}\` \n*The following error was logged:* \`\`\`${stderr.trim()}\`\`\`
30+
`)
31+
32+
const mail = {
33+
subject: `Deployment for ${Config.name} failed ❌`,
34+
data: (`
35+
<h3>Deployment for ${Config.name} failed 😢 </h3>
36+
<br/>
37+
<b>Date</b> <date>${new Date().toUTCString()}</date> <br/>
38+
<b>The following error was logged:</b> <code color="red">${stderr.trim()}</code>
39+
`)
40+
}
41+
42+
/** Send error notification */
43+
new NotificationService({ slack, mail }, 'error').sendNotifications()
44+
45+
/**
46+
* Write error log
47+
*/
48+
fs.writeFileSync('error.log', `${new Date().toUTCString()}, Config: ${Config.name}, Error: ${stderr.trim()}`, {
1549
encoding: 'utf-8',
1650
flag: 'a',
1751
})
1852
}
1953
})
2054
}
2155

56+
2257
module.exports = deployment

services/mail.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const mailer = require('nodemailer')
2+
const mail = require('../config/mail')
3+
const transport = mailer.createTransport({
4+
host: mail.SMTP_HOST,
5+
port: mail.SMTP_PORT,
6+
auth: {
7+
user: mail.SMTP_USERNAME,
8+
pass: mail.SMTP_PASSWORD
9+
}
10+
})
11+
12+
module.exports = transport

services/notifications.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* ------------------------------------------------------------------------
3+
* This file contains the notification services that are supported
4+
* This includes Slack,Email,Telegram
5+
* ------------------------------------------------------------------------
6+
*/
7+
const config = require('../config/notifications')
8+
const https = require('https')
9+
const transport = require('./mail')
10+
11+
class NotificationService {
12+
/**
13+
* Notification service is enabled
14+
*/
15+
notificationEnabled = Boolean
16+
/**
17+
* The Slack Webhook URL
18+
*/
19+
slackURL = String
20+
/**
21+
* Successful build notification
22+
*/
23+
notifyOnSuccess = Boolean
24+
/**
25+
* Error Notifications
26+
*/
27+
notifyOnError = Boolean
28+
/**
29+
* The email for notifications
30+
*/
31+
notificationEmail = String
32+
33+
/**
34+
* The notification payload
35+
*/
36+
notificationData = String
37+
38+
/**
39+
* The notification type
40+
*/
41+
notificationType = Boolean
42+
43+
/**
44+
* initialise the service
45+
* @param {Array} data
46+
* @param {String} type
47+
*/
48+
constructor(data = {}, type = '') {
49+
this.notificationEnabled = config.allowNotifications
50+
this.slackURL = config.slackWebhookURL
51+
this.notifyOnError = config.notifyOnErrors
52+
this.notifyOnSuccess = config.notifyOnSucess
53+
this.notificationEmail = config.notificationEmail
54+
this.notificationData = data
55+
this.notificationType = type
56+
}
57+
58+
/**
59+
* Send notifications
60+
* @param {Array} services
61+
* @returns void
62+
*/
63+
sendNotifications() {
64+
if (!this.notificationEnabled) {
65+
return
66+
}
67+
if (this.notificationType === 'success' && this.notifyOnSuccess === 'false') {
68+
return
69+
}
70+
if (this.notificationType === 'error' && this.notifyOnError === 'false') {
71+
return
72+
}
73+
const services = this.getSupportedServices()
74+
services.forEach((service) => {
75+
switch (service.name) {
76+
case 'Slack':
77+
this.sendSlackWebhook(service.value)
78+
break;
79+
case 'email':
80+
this.sendEmails()
81+
break;
82+
default:
83+
break;
84+
}
85+
})
86+
}
87+
88+
/**
89+
* Get the supported services
90+
* @returns Array []
91+
*/
92+
getSupportedServices() {
93+
const supportedServices = []
94+
if (this.slackURL) {
95+
supportedServices.push({
96+
name: 'Slack',
97+
value: this.slackURL
98+
})
99+
}
100+
if (this.notificationEmail) {
101+
supportedServices.push({
102+
name: 'email', value: this.notificationEmail
103+
})
104+
}
105+
return supportedServices
106+
}
107+
108+
/**
109+
* Send slack webhook
110+
* @param {String} url
111+
* @returns Boolean
112+
*/
113+
sendSlackWebhook(url = '') {
114+
const fullURL = new URL(url)
115+
const payload = Buffer.from(JSON.stringify({
116+
text: this.notificationData.slack,
117+
mrkdwn: true
118+
}))
119+
const request = https.request({
120+
host: fullURL.host,
121+
path: fullURL.pathname,
122+
method: 'POST',
123+
port: 443,
124+
protocol: 'https:'
125+
}, (res) => {
126+
res.setEncoding('utf-8');
127+
})
128+
request.on('error', (err) => {
129+
throw err
130+
})
131+
request.write(payload)
132+
request.end()
133+
}
134+
135+
/**
136+
* Send Email(s)
137+
* @param {String} emails
138+
* @returns Boolean
139+
*/
140+
sendEmails() {
141+
transport.sendMail({
142+
from: config.notificationFrom,
143+
to: config.notificationEmail,
144+
subject: this.notificationData.mail.subject,
145+
html: this.notificationData.mail.data,
146+
});
147+
return true
148+
}
149+
}
150+
151+
module.exports = NotificationService

0 commit comments

Comments
 (0)