diff --git a/cmd/goliac/main.go b/cmd/goliac/main.go index 9882f09..e3eb73e 100644 --- a/cmd/goliac/main.go +++ b/cmd/goliac/main.go @@ -6,6 +6,7 @@ import ( "github.com/Alayacare/goliac/internal" "github.com/Alayacare/goliac/internal/config" + "github.com/Alayacare/goliac/internal/notification" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -170,7 +171,13 @@ any changes from the teams Git repository to Github.`, if err != nil { logrus.Fatalf("failed to create goliac: %s", err) } - server := internal.NewGoliacServer(goliac) + notificationService := notification.NewNullNotificationService() + if config.Config.SlackToken != "" && config.Config.SlackChannel != "" { + slackService := notification.NewSlackNotificationService(config.Config.SlackToken, config.Config.SlackChannel) + notificationService = slackService + } + + server := internal.NewGoliacServer(goliac, notificationService) server.Serve() }, } diff --git a/docs/installation.md b/docs/installation.md index 63ffdcb..bfdb2fd 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -206,6 +206,8 @@ You can run the goliac server as a service or a docker container. It needs sever | GOLIAC_SERVER_PORT | 18000 | | | GOLIAC_SERVER_GIT_BRANCH_PROTECTION_REQUIRED_CHECK | validate | ci check to enforce when evaluating a PR (used for CI mode) | | GOLIAC_MAX_CHANGESETS_OVERRIDE | false | if you need to override the `max_changesets` setting in the `goliac.yaml` file. Useful in particular using the `goliac apply` CLI | +| GOLIAC_SLACK_TOKEN | | Slack token to send notification | +| GOLIAC_SLACK_CHANNEL | | Slack channel to send notification | then you just need to start it with diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 3976915..b3a071e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -5,7 +5,7 @@ This error is happening if a changeset (a team's PR) introduce more than X changesets. This is a safety mechanism to avoid applying a huge number of changesets at once. If it is a legitimate change, you can -- either create a new PR to reduce the number of changesets in the original PR to stay below the limit, Goliac will automatically apply the cumulative changesets. +- either increase the `max_changesets` in the `goliac.yaml` file, but that's not the best approach. - or you can use the CLI to force apply the changesets. To do so, you can run the following command: ```bash diff --git a/internal/config/env.go b/internal/config/env.go index 07b23a2..636497a 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -52,4 +52,8 @@ var Config = struct { // UI path => localhost:18000/foo" // API path => localhost:18000/foo/api/v1" WebPrefix string `env:"GOLIAC_WEB_PREFIX" envDefault:""` + + // to receive slack notifications on errors + SlackToken string `env:"GOLIAC_SLACK_TOKEN" envDefault:""` + SlackChannel string `env:"GOLIAC_SLACK_CHANNEL" envDefault:""` }{} diff --git a/internal/goliac_server.go b/internal/goliac_server.go index 518c159..7b441ba 100644 --- a/internal/goliac_server.go +++ b/internal/goliac_server.go @@ -10,6 +10,7 @@ import ( "github.com/Alayacare/goliac/internal/config" "github.com/Alayacare/goliac/internal/entity" + "github.com/Alayacare/goliac/internal/notification" "github.com/Alayacare/goliac/swagger_gen/models" "github.com/Alayacare/goliac/swagger_gen/restapi" "github.com/Alayacare/goliac/swagger_gen/restapi/operations" @@ -44,21 +45,24 @@ type GoliacServer interface { } type GoliacServerImpl struct { - goliac Goliac - applyLobbyMutex sync.Mutex - applyLobbyCond *sync.Cond - applyCurrent bool - applyLobby bool - ready bool // when the server has finished to load the local configuration - lastSyncTime *time.Time - lastSyncError error - syncInterval int64 // in seconds time remaining between 2 sync + goliac Goliac + applyLobbyMutex sync.Mutex + applyLobbyCond *sync.Cond + applyCurrent bool + applyLobby bool + ready bool // when the server has finished to load the local configuration + lastSyncTime *time.Time + lastSyncError error + syncInterval int64 // in seconds time remaining between 2 sync + notificationService notification.NotificationService } -func NewGoliacServer(goliac Goliac) GoliacServer { +func NewGoliacServer(goliac Goliac, notificationService notification.NotificationService) GoliacServer { + server := GoliacServerImpl{ - goliac: goliac, - ready: false, + goliac: goliac, + ready: false, + notificationService: notificationService, } server.applyLobbyCond = sync.NewCond(&server.applyLobbyMutex) @@ -507,6 +511,9 @@ func (g *GoliacServerImpl) Serve() { // log the error only if it's a new one if err != nil && (previousError == nil || err.Error() != previousError.Error()) { logrus.Error(err) + if err := g.notificationService.SendNotification(fmt.Sprintf("Goliac error when syncing: %s", err)); err != nil { + logrus.Error(err) + } } g.syncInterval = config.Config.ServerApplyInterval } diff --git a/internal/notification/notification.go b/internal/notification/notification.go new file mode 100644 index 0000000..d825084 --- /dev/null +++ b/internal/notification/notification.go @@ -0,0 +1,16 @@ +package notification + +type NotificationService interface { + SendNotification(message string) error +} + +type NullNotificationService struct { +} + +func NewNullNotificationService() NotificationService { + return &NullNotificationService{} +} + +func (s *NullNotificationService) SendNotification(message string) error { + return nil +} diff --git a/internal/notification/slacknotifiction.go b/internal/notification/slacknotifiction.go new file mode 100644 index 0000000..55f0951 --- /dev/null +++ b/internal/notification/slacknotifiction.go @@ -0,0 +1,66 @@ +package notification + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +type SlackNotificationService struct { + SlackToken string + Channel string +} + +func NewSlackNotificationService(slackToken string, channel string) NotificationService { + return &SlackNotificationService{ + SlackToken: slackToken, + Channel: channel, + } +} + +type SlackMessage struct { + Channel string `json:"channel"` + Text string `json:"text"` +} + +func (s *SlackNotificationService) SendNotification(message string) error { + url := "https://slack.com/api/chat.postMessage" + + // Prepare the message payload + msg := SlackMessage{ + Channel: s.Channel, + Text: message, + } + + // Convert the payload to JSON + jsonPayload, err := json.Marshal(msg) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %v", err) + } + + // Create a new HTTP POST request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload)) + if err != nil { + return fmt.Errorf("failed to create new request: %v", err) + } + + // Set the required headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+s.SlackToken) + + // Make the HTTP request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + // Check the response from Slack API + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-200 response: %v", resp.Status) + } + + return nil +}