Skip to content

Commit f7466f7

Browse files
authored
Merge pull request #6526 from devtron-labs/notifier-refac
feat: Enable selection of all CI pipelines at once when the Environment filter is applied in Notifications
2 parents edc0f6a + fa3b9d4 commit f7466f7

File tree

10 files changed

+465
-114
lines changed

10 files changed

+465
-114
lines changed

api/restHandler/NotificationRestHandler.go

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const (
5151
)
5252

5353
type NotificationRestHandler interface {
54-
SaveNotificationSettings(w http.ResponseWriter, r *http.Request)
5554
SaveNotificationSettingsV2(w http.ResponseWriter, r *http.Request)
5655
UpdateNotificationSettings(w http.ResponseWriter, r *http.Request)
5756
SaveNotificationChannelConfig(w http.ResponseWriter, r *http.Request)
@@ -118,66 +117,6 @@ func NewNotificationRestHandlerImpl(dockerRegistryConfig pipeline.DockerRegistry
118117
}
119118
}
120119

121-
// SaveNotificationSettings will be deprecated in future
122-
func (impl NotificationRestHandlerImpl) SaveNotificationSettings(w http.ResponseWriter, r *http.Request) {
123-
userId, err := impl.userAuthService.GetLoggedInUser(r)
124-
if userId == 0 || err != nil {
125-
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
126-
return
127-
}
128-
var notificationSetting beans.NotificationRequest
129-
err = json.NewDecoder(r.Body).Decode(&notificationSetting)
130-
if err != nil {
131-
impl.logger.Errorw("request err, SaveNotificationSettings", "err", err, "payload", notificationSetting)
132-
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
133-
return
134-
}
135-
impl.logger.Infow("request payload, SaveNotificationSettings", "err", err, "payload", notificationSetting)
136-
err = impl.validator.Struct(notificationSetting)
137-
if err != nil {
138-
impl.logger.Errorw("validation err, SaveNotificationSettings", "err", err, "payload", notificationSetting)
139-
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
140-
return
141-
}
142-
143-
//RBAC
144-
token := r.Header.Get("token")
145-
if isSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !isSuperAdmin {
146-
common.WriteJsonResp(w, err, nil, http.StatusForbidden)
147-
return
148-
}
149-
//RBAC
150-
151-
providers := notificationSetting.Providers
152-
153-
if len(providers) != 0 {
154-
for _, provider := range providers {
155-
if provider.Destination == util.SMTP || provider.Destination == util.SES {
156-
if provider.Recipient == "" {
157-
userEmail, err := impl.userAuthService.GetEmailById(int32(provider.ConfigId))
158-
if err != nil {
159-
impl.logger.Errorw("service err, SaveNotificationSettings", "err", err, "payload", notificationSetting)
160-
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
161-
return
162-
}
163-
provider.Recipient = userEmail
164-
}
165-
// get default configID for SES and SMTP
166-
provider.ConfigId = notificationSetting.SesConfigId
167-
}
168-
}
169-
}
170-
171-
res, err := impl.notificationService.CreateOrUpdateNotificationSettings(&notificationSetting, userId)
172-
if err != nil {
173-
impl.logger.Errorw("service err, SaveNotificationSettings", "err", err, "payload", notificationSetting)
174-
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
175-
return
176-
}
177-
w.Header().Set("Content-Type", "application/json")
178-
common.WriteJsonResp(w, nil, res, http.StatusOK)
179-
}
180-
181120
func (impl NotificationRestHandlerImpl) SaveNotificationSettingsV2(w http.ResponseWriter, r *http.Request) {
182121
userId, err := impl.userAuthService.GetLoggedInUser(r)
183122
if userId == 0 || err != nil {

api/router/NotificationRouter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ func NewNotificationRouterImpl(notificationRestHandler restHandler.NotificationR
3232
return &NotificationRouterImpl{notificationRestHandler: notificationRestHandler}
3333
}
3434
func (impl NotificationRouterImpl) InitNotificationRegRouter(configRouter *mux.Router) {
35-
// to maintain backward compatibility, will be deprecated in future
35+
// do not remove it, this path is used in devtcl cli
3636
configRouter.Path("").
37-
HandlerFunc(impl.notificationRestHandler.SaveNotificationSettings).
37+
HandlerFunc(impl.notificationRestHandler.SaveNotificationSettingsV2).
3838
Methods("POST")
3939
// new router to save notification settings
4040
configRouter.Path("/v2").

client/events/EventBuilder.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,26 @@ func (impl *EventSimpleFactoryImpl) BuildExtraCIData(event Event, material *buil
206206
payload.TriggeredBy = user.EmailId
207207
event.Payload = payload
208208
}
209+
210+
// fetching all the envs which are directly or indirectly linked with the ci pipeline
211+
if event.PipelineId > 0 {
212+
// Get the pipeline to check if it's external
213+
ciPipeline, err := impl.ciPipelineRepository.FindById(event.PipelineId)
214+
if err != nil {
215+
impl.logger.Errorw("error in getting ci pipeline", "pipelineId", event.PipelineId, "err", err)
216+
} else {
217+
envs, err := impl.envRepository.FindEnvLinkedWithCiPipelines(ciPipeline.IsExternal, []int{event.PipelineId})
218+
if err != nil {
219+
impl.logger.Errorw("error in finding environments linked with ci pipeline", "pipelineId", event.PipelineId, "err", err)
220+
} else {
221+
event.EnvIdsForCiPipeline = make([]int, 0, len(envs))
222+
for _, env := range envs {
223+
event.EnvIdsForCiPipeline = append(event.EnvIdsForCiPipeline, env.Id)
224+
}
225+
}
226+
}
227+
}
228+
209229
return event
210230
}
211231

client/events/EventClient.go

Lines changed: 142 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package client
1818

1919
import (
2020
"bytes"
21+
"context"
2122
"encoding/json"
2223
"errors"
2324
"fmt"
@@ -38,6 +39,7 @@ import (
3839
type EventClientConfig struct {
3940
DestinationURL string `env:"EVENT_URL" envDefault:"http://localhost:3000/notify" description:"Notifier service url"`
4041
NotificationMedium NotificationMedium `env:"NOTIFICATION_MEDIUM" envDefault:"rest" description:"notification medium"`
42+
EnableNotifierV2 bool `env:"ENABLE_NOTIFIER_V2" envDefault:"false" description:"enable notifier v2"`
4143
}
4244
type NotificationMedium string
4345

@@ -58,24 +60,25 @@ type EventClient interface {
5860
}
5961

6062
type Event struct {
61-
EventTypeId int `json:"eventTypeId"`
62-
EventName string `json:"eventName"`
63-
PipelineId int `json:"pipelineId"`
64-
PipelineType string `json:"pipelineType"`
65-
CorrelationId string `json:"correlationId"`
66-
Payload *Payload `json:"payload"`
67-
EventTime string `json:"eventTime"`
68-
TeamId int `json:"teamId"`
69-
AppId int `json:"appId"`
70-
EnvId int `json:"envId"`
71-
IsProdEnv bool `json:"isProdEnv"`
72-
ClusterId int `json:"clusterId"`
73-
CdWorkflowType bean.WorkflowType `json:"cdWorkflowType,omitempty"`
74-
CdWorkflowRunnerId int `json:"cdWorkflowRunnerId"`
75-
CiWorkflowRunnerId int `json:"ciWorkflowRunnerId"`
76-
CiArtifactId int `json:"ciArtifactId"`
77-
BaseUrl string `json:"baseUrl"`
78-
UserId int `json:"-"`
63+
EventTypeId int `json:"eventTypeId"`
64+
EventName string `json:"eventName"`
65+
PipelineId int `json:"pipelineId"`
66+
PipelineType string `json:"pipelineType"`
67+
CorrelationId string `json:"correlationId"`
68+
Payload *Payload `json:"payload"`
69+
EventTime string `json:"eventTime"`
70+
TeamId int `json:"teamId"`
71+
AppId int `json:"appId"`
72+
EnvId int `json:"envId"`
73+
IsProdEnv bool `json:"isProdEnv"`
74+
ClusterId int `json:"clusterId"`
75+
CdWorkflowType bean.WorkflowType `json:"cdWorkflowType,omitempty"`
76+
CdWorkflowRunnerId int `json:"cdWorkflowRunnerId"`
77+
CiWorkflowRunnerId int `json:"ciWorkflowRunnerId"`
78+
CiArtifactId int `json:"ciArtifactId"`
79+
EnvIdsForCiPipeline []int `json:"envIdsForCiPipeline"`
80+
BaseUrl string `json:"baseUrl"`
81+
UserId int `json:"-"`
7982
}
8083

8184
type Payload struct {
@@ -95,22 +98,25 @@ type Payload struct {
9598
}
9699

97100
type EventRESTClientImpl struct {
98-
logger *zap.SugaredLogger
99-
client *http.Client
100-
config *EventClientConfig
101-
pubsubClient *pubsub.PubSubClientServiceImpl
102-
ciPipelineRepository pipelineConfig.CiPipelineRepository
103-
pipelineRepository pipelineConfig.PipelineRepository
104-
attributesRepository repository.AttributesRepository
105-
moduleService module.ModuleService
101+
logger *zap.SugaredLogger
102+
client *http.Client
103+
config *EventClientConfig
104+
pubsubClient *pubsub.PubSubClientServiceImpl
105+
ciPipelineRepository pipelineConfig.CiPipelineRepository
106+
pipelineRepository pipelineConfig.PipelineRepository
107+
attributesRepository repository.AttributesRepository
108+
moduleService module.ModuleService
109+
notificationSettingsRepository repository.NotificationSettingsRepository
106110
}
107111

108112
func NewEventRESTClientImpl(logger *zap.SugaredLogger, client *http.Client, config *EventClientConfig, pubsubClient *pubsub.PubSubClientServiceImpl,
109113
ciPipelineRepository pipelineConfig.CiPipelineRepository, pipelineRepository pipelineConfig.PipelineRepository,
110-
attributesRepository repository.AttributesRepository, moduleService module.ModuleService) *EventRESTClientImpl {
114+
attributesRepository repository.AttributesRepository, moduleService module.ModuleService,
115+
notificationSettingsRepository repository.NotificationSettingsRepository) *EventRESTClientImpl {
111116
return &EventRESTClientImpl{logger: logger, client: client, config: config, pubsubClient: pubsubClient,
112117
ciPipelineRepository: ciPipelineRepository, pipelineRepository: pipelineRepository,
113-
attributesRepository: attributesRepository, moduleService: moduleService}
118+
attributesRepository: attributesRepository, moduleService: moduleService,
119+
notificationSettingsRepository: notificationSettingsRepository}
114120
}
115121

116122
func (impl *EventRESTClientImpl) buildFinalPayload(event Event, cdPipeline *pipelineConfig.Pipeline, ciPipeline *pipelineConfig.CiPipeline) *Payload {
@@ -235,34 +241,131 @@ func (impl *EventRESTClientImpl) sendEventsOnNats(body []byte) error {
235241
// do not call this method if notification module is not installed
236242
func (impl *EventRESTClientImpl) sendEvent(event Event) (bool, error) {
237243
impl.logger.Debugw("event before send", "event", event)
238-
body, err := json.Marshal(event)
244+
245+
// Step 1: Create payload and destination URL based on config
246+
bodyBytes, destinationUrl, err := impl.createPayloadAndDestination(event)
239247
if err != nil {
240-
impl.logger.Errorw("error while marshaling event request ", "err", err)
241248
return false, err
242249
}
250+
251+
// Step 2: Send via appropriate medium (NATS or REST)
252+
return impl.deliverEvent(bodyBytes, destinationUrl)
253+
}
254+
255+
func (impl *EventRESTClientImpl) createPayloadAndDestination(event Event) ([]byte, string, error) {
256+
if impl.config.EnableNotifierV2 {
257+
return impl.createV2PayloadAndDestination(event)
258+
}
259+
return impl.createDefaultPayloadAndDestination(event)
260+
}
261+
262+
func (impl *EventRESTClientImpl) createV2PayloadAndDestination(event Event) ([]byte, string, error) {
263+
destinationUrl := impl.config.DestinationURL + "/v2"
264+
265+
// Fetch notification settings
266+
req := repository.GetRulesRequest{
267+
TeamId: event.TeamId,
268+
EnvId: event.EnvId,
269+
AppId: event.AppId,
270+
PipelineId: event.PipelineId,
271+
PipelineType: event.PipelineType,
272+
IsProdEnv: &event.IsProdEnv,
273+
ClusterId: event.ClusterId,
274+
EnvIdsForCiPipeline: event.EnvIdsForCiPipeline,
275+
}
276+
notificationSettings, err := impl.notificationSettingsRepository.FindNotificationSettingsWithRules(
277+
context.Background(), event.EventTypeId, req,
278+
)
279+
if err != nil {
280+
impl.logger.Errorw("error while fetching notification settings", "err", err)
281+
return nil, "", err
282+
}
283+
284+
// Process notification settings into beans
285+
notificationSettingsBean, err := impl.processNotificationSettings(notificationSettings)
286+
if err != nil {
287+
return nil, "", err
288+
}
289+
290+
// Create combined payload
291+
combinedPayload := map[string]interface{}{
292+
"event": event,
293+
"notificationSettings": notificationSettingsBean,
294+
}
295+
296+
bodyBytes, err := json.Marshal(combinedPayload)
297+
if err != nil {
298+
impl.logger.Errorw("error while marshaling combined event request", "err", err)
299+
return nil, "", err
300+
}
301+
302+
return bodyBytes, destinationUrl, nil
303+
}
304+
305+
func (impl *EventRESTClientImpl) createDefaultPayloadAndDestination(event Event) ([]byte, string, error) {
306+
bodyBytes, err := json.Marshal(event)
307+
if err != nil {
308+
impl.logger.Errorw("error while marshaling event request", "err", err)
309+
return nil, "", err
310+
}
311+
return bodyBytes, impl.config.DestinationURL, nil
312+
}
313+
314+
func (impl *EventRESTClientImpl) processNotificationSettings(notificationSettings []repository.NotificationSettings) ([]*repository.NotificationSettingsBean, error) {
315+
notificationSettingsBean := make([]*repository.NotificationSettingsBean, 0)
316+
for _, item := range notificationSettings {
317+
config := make([]repository.ConfigEntry, 0)
318+
if item.Config != "" {
319+
if err := json.Unmarshal([]byte(item.Config), &config); err != nil {
320+
impl.logger.Errorw("error while unmarshaling config", "err", err)
321+
return nil, err
322+
}
323+
}
324+
notificationSettingsBean = append(notificationSettingsBean, &repository.NotificationSettingsBean{
325+
Id: item.Id,
326+
TeamId: item.TeamId,
327+
AppId: item.AppId,
328+
EnvId: item.EnvId,
329+
PipelineId: item.PipelineId,
330+
PipelineType: item.PipelineType,
331+
EventTypeId: item.EventTypeId,
332+
Config: config,
333+
ViewId: item.ViewId,
334+
})
335+
}
336+
return notificationSettingsBean, nil
337+
}
338+
339+
func (impl *EventRESTClientImpl) deliverEvent(bodyBytes []byte, destinationUrl string) (bool, error) {
243340
if impl.config.NotificationMedium == PUB_SUB {
244-
err = impl.sendEventsOnNats(body)
245-
if err != nil {
246-
impl.logger.Errorw("error while publishing event ", "err", err)
341+
if err := impl.sendEventsOnNats(bodyBytes); err != nil {
342+
impl.logger.Errorw("error while publishing event", "err", err)
247343
return false, err
248344
}
249345
return true, nil
250346
}
251-
var reqBody = []byte(body)
252-
req, err := http.NewRequest(http.MethodPost, impl.config.DestinationURL, bytes.NewBuffer(reqBody))
347+
348+
req, err := http.NewRequest(http.MethodPost, destinationUrl, bytes.NewBuffer(bodyBytes))
253349
if err != nil {
254-
impl.logger.Errorw("error while writing event", "err", err)
350+
impl.logger.Errorw("error while creating HTTP request", "err", err)
255351
return false, err
256352
}
257353
req.Header.Set("Content-Type", "application/json")
354+
258355
resp, err := impl.client.Do(req)
259356
if err != nil {
260-
impl.logger.Errorw("error while UpdateJiraTransition request ", "err", err)
357+
impl.logger.Errorw("error while sending HTTP request", "err", err)
261358
return false, err
262359
}
263360
defer resp.Body.Close()
264-
impl.logger.Debugw("event completed", "event resp", resp)
265-
return true, err
361+
362+
if resp.StatusCode >= 300 {
363+
impl.logger.Errorw("unexpected response from notifier", "status", resp.StatusCode)
364+
return false, fmt.Errorf("unexpected response code: %d", resp.StatusCode)
365+
}
366+
367+
impl.logger.Debugw("event successfully delivered", "status", resp.StatusCode)
368+
return true, nil
266369
}
267370

268371
func (impl *EventRESTClientImpl) WriteNatsEvent(topic string, payload interface{}) error {

env_gen.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

env_gen.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
| ECR_REPO_NAME_PREFIX | string |test/ | Prefix for ECR repo to be created in does not exist | | false |
175175
| ENABLE_ASYNC_ARGO_CD_INSTALL_DEVTRON_CHART | bool |false | To enable async installation of gitops application | | false |
176176
| ENABLE_ASYNC_INSTALL_DEVTRON_CHART | bool |false | To enable async installation of no-gitops application | | false |
177+
| ENABLE_NOTIFIER_V2 | bool |false | enable notifier v2 | | false |
177178
| EPHEMERAL_SERVER_VERSION_REGEX | string |v[1-9]\.\b(2[3-9]\|[3-9][0-9])\b.* | ephemeral containers support version regex that is compared with k8sServerVersion | | false |
178179
| EVENT_URL | string |http://localhost:3000/notify | Notifier service url | | false |
179180
| EXECUTE_WIRE_NIL_CHECKER | bool |false | checks for any nil pointer in wire.go | | false |

0 commit comments

Comments
 (0)