Skip to content

Commit 04a9c67

Browse files
cursoragentfiftin
andcommitted
Add new notification system with service-based architecture
Co-authored-by: denguk <denguk@gmail.com>
1 parent be26c58 commit 04a9c67

7 files changed

Lines changed: 730 additions & 0 deletions

File tree

NOTIFICATION_SYSTEM.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# New Notification System
2+
3+
## Overview
4+
5+
Semaphore now includes a new, more secure notification system that uses a service approach with a common interface for all notification methods. This new system provides better security by separating tokens from URLs and using dedicated configuration fields.
6+
7+
## Key Features
8+
9+
- **Service-oriented architecture**: Common interface for all notification methods
10+
- **Enhanced security**: Separate token and channel/URL parameters (no more tokens in URLs)
11+
- **Backward compatibility**: Old notification methods continue to work
12+
- **Unified configuration**: All notification settings in one place
13+
- **Error handling**: Better error reporting and logging
14+
15+
## Supported Notification Methods
16+
17+
1. **Telegram** - Uses bot token and chat ID
18+
2. **Slack** - Uses webhook URL
19+
3. **Gotify** - Uses server URL and application token
20+
4. **DingTalk** - Uses webhook URL
21+
5. **RocketChat** - Uses webhook URL
22+
6. **Microsoft Teams** - Uses webhook URL
23+
24+
## Configuration
25+
26+
### New Configuration Format
27+
28+
Add the `notifications` section to your `config.json`:
29+
30+
```json
31+
{
32+
"notifications": {
33+
"telegram": {
34+
"enabled": true,
35+
"token": "your_telegram_bot_token",
36+
"channel": "your_telegram_chat_id"
37+
},
38+
"slack": {
39+
"enabled": true,
40+
"url": "https://hooks.slack.com/services/your/webhook/url"
41+
},
42+
"gotify": {
43+
"enabled": true,
44+
"token": "your_gotify_app_token",
45+
"url": "https://gotify.example.com"
46+
},
47+
"dingtalk": {
48+
"enabled": true,
49+
"url": "https://oapi.dingtalk.com/robot/send?access_token=your_token"
50+
},
51+
"rocketchat": {
52+
"enabled": true,
53+
"url": "https://rocketchat.example.com/hooks/webhook_id/webhook_token"
54+
},
55+
"microsoft_teams": {
56+
"enabled": true,
57+
"url": "https://outlook.office.com/webhook/your/webhook/url"
58+
}
59+
}
60+
}
61+
```
62+
63+
### Configuration Fields
64+
65+
Each notification method supports the following fields:
66+
67+
- `enabled` (boolean): Whether this notification method is active
68+
- `token` (string): Authentication token (for Telegram and Gotify)
69+
- `channel` (string): Channel/Chat ID (for Telegram)
70+
- `url` (string): Webhook or server URL (for all other methods)
71+
72+
## Security Improvements
73+
74+
### Before (Old System)
75+
```json
76+
{
77+
"gotify_alert": true,
78+
"gotify_url": "https://gotify.example.com/message?token=insecure_token_in_url"
79+
}
80+
```
81+
82+
### After (New System)
83+
```json
84+
{
85+
"notifications": {
86+
"gotify": {
87+
"enabled": true,
88+
"token": "secure_token_separate_from_url",
89+
"url": "https://gotify.example.com"
90+
}
91+
}
92+
}
93+
```
94+
95+
## Backward Compatibility
96+
97+
The old notification system continues to work alongside the new one. You can:
98+
99+
1. **Use only the old system**: Keep your existing configuration
100+
2. **Use only the new system**: Add the `notifications` section
101+
3. **Use both systems**: Both will send notifications (not recommended for production)
102+
103+
### Migration Path
104+
105+
1. **Keep existing config** for immediate backward compatibility
106+
2. **Add new `notifications` section** with your preferred settings
107+
3. **Test the new system** to ensure it works as expected
108+
4. **Remove old notification settings** once you're satisfied
109+
110+
## Implementation Details
111+
112+
### Architecture
113+
114+
The new system uses a service-oriented architecture with:
115+
116+
- **Common Interface**: `Notifier` interface that all notification methods implement
117+
- **Service Class**: `NotificationService` that manages all notifiers
118+
- **Configuration**: Centralized configuration in `util.NotificationConfigs`
119+
- **Template System**: Reuses existing notification templates
120+
121+
### Code Structure
122+
123+
```
124+
services/tasks/
125+
├── notification_service.go # Main service implementation
126+
├── notification_service_test.go # Unit tests
127+
└── alert.go # Integration with existing system
128+
```
129+
130+
### Key Components
131+
132+
1. **Notifier Interface**:
133+
```go
134+
type Notifier interface {
135+
Send(alert Alert, config util.NotificationConfig) error
136+
GetTemplateName() string
137+
}
138+
```
139+
140+
2. **NotificationService**:
141+
- Manages all notification methods
142+
- Handles enabled/disabled states
143+
- Provides error logging
144+
145+
3. **Individual Notifiers**:
146+
- TelegramNotifier
147+
- SlackNotifier
148+
- GotifyNotifier
149+
- DingTalkNotifier
150+
- RocketChatNotifier
151+
- MicrosoftTeamsNotifier
152+
153+
## Usage Examples
154+
155+
### Telegram Setup
156+
157+
1. Create a Telegram bot via @BotFather
158+
2. Get your bot token
159+
3. Get your chat ID (can be user ID or group ID)
160+
4. Configure:
161+
```json
162+
{
163+
"notifications": {
164+
"telegram": {
165+
"enabled": true,
166+
"token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
167+
"channel": "-1001234567890"
168+
}
169+
}
170+
}
171+
```
172+
173+
### Slack Setup
174+
175+
1. Create a Slack webhook in your workspace
176+
2. Copy the webhook URL
177+
3. Configure:
178+
```json
179+
{
180+
"notifications": {
181+
"slack": {
182+
"enabled": true,
183+
"url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
184+
}
185+
}
186+
}
187+
```
188+
189+
### Gotify Setup
190+
191+
1. Install Gotify server
192+
2. Create an application in Gotify
193+
3. Get the application token
194+
4. Configure:
195+
```json
196+
{
197+
"notifications": {
198+
"gotify": {
199+
"enabled": true,
200+
"token": "your_gotify_app_token",
201+
"url": "https://gotify.yourdomain.com"
202+
}
203+
}
204+
}
205+
```
206+
207+
## Testing
208+
209+
Run the notification tests:
210+
211+
```bash
212+
go test -v ./services/tasks -run "Test.*Notifier"
213+
```
214+
215+
## Troubleshooting
216+
217+
### Common Issues
218+
219+
1. **No notifications received**:
220+
- Check if `enabled: true` is set
221+
- Verify token/URL configuration
222+
- Check Semaphore logs for error messages
223+
224+
2. **Telegram not working**:
225+
- Ensure bot token is correct
226+
- Verify chat ID (use negative ID for groups)
227+
- Bot must be added to the group/channel
228+
229+
3. **Webhook URLs not working**:
230+
- Verify URL is accessible from Semaphore server
231+
- Check if webhook is still active in the service
232+
- Test webhook manually with curl
233+
234+
### Logging
235+
236+
The new system provides detailed error logging. Check Semaphore logs for messages like:
237+
- `telegram notification failed: ...`
238+
- `slack notification failed: ...`
239+
- etc.
240+
241+
## Future Enhancements
242+
243+
Planned improvements:
244+
- Environment variable support for notification configs
245+
- Template customization per notification method
246+
- Rate limiting and retry mechanisms
247+
- Additional notification providers (Discord, Matrix, etc.)

services/tasks/TaskRunner_logging.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ func (t *TaskRunner) SetStatus(status task_logger.TaskStatus) {
122122
}
123123

124124
if status.IsNotifiable() {
125+
// Try new notification system first
126+
t.sendNotificationsWithNewService()
127+
128+
// Fall back to old notification system for backward compatibility
125129
t.sendTelegramAlert()
126130
t.sendSlackAlert()
127131
t.sendRocketChatAlert()

services/tasks/alert.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,42 @@ func (t *TaskRunner) taskLink() string {
559559
t.Task.ID,
560560
)
561561
}
562+
563+
// sendNotificationsWithNewService sends notifications using the new service approach
564+
func (t *TaskRunner) sendNotificationsWithNewService() {
565+
if !t.alert {
566+
return
567+
}
568+
569+
// Check if new notification system is configured
570+
if util.Config.Notifications == nil {
571+
return
572+
}
573+
574+
author, version := t.alertInfos()
575+
576+
alert := Alert{
577+
Name: t.Template.Name,
578+
Author: author,
579+
Color: t.alertColor(""),
580+
Task: alertTask{
581+
ID: strconv.Itoa(t.Task.ID),
582+
URL: t.taskLink(),
583+
Result: t.Task.Status.Format(),
584+
Version: version,
585+
Desc: t.Task.Message,
586+
},
587+
Chat: alertChat{
588+
ID: util.Config.TelegramChat, // Default chat ID for backward compatibility
589+
},
590+
}
591+
592+
// Override chat ID if specified in template
593+
if t.alertChat != nil && *t.alertChat != "" {
594+
alert.Chat.ID = *t.alertChat
595+
}
596+
597+
// Create and use the notification service
598+
service := NewNotificationService(*util.Config.Notifications)
599+
service.SendNotifications(alert, t.Template.SuppressSuccessAlerts)
600+
}

services/tasks/alert_test_sender.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ func SendProjectTestAlerts(project db.Project, store db.Store) (err error) {
4040
},
4141
}
4242

43+
// Try new notification system first
44+
tr.sendNotificationsWithNewService()
45+
46+
// Fall back to old notification system for backward compatibility
4347
tr.sendTelegramAlert()
4448
tr.sendSlackAlert()
4549
tr.sendRocketChatAlert()

0 commit comments

Comments
 (0)