chore: fix infinity calling Fatal, add a backup reference

This commit is contained in:
Jonas Kaninda
2024-10-10 04:14:42 +02:00
parent df490af7b6
commit e5dd7e76ce
9 changed files with 133 additions and 59 deletions

View File

@@ -4,10 +4,10 @@ layout: default
parent: How Tos parent: How Tos
nav_order: 12 nav_order: 12
--- ---
Send Email or Telegram notifications on success or failed backup. Send Email or Telegram notifications on successfully or failed backup.
### Email ### Email
To send out email notifications on failed backup runs, provide SMTP credentials, a sender and a recipient: To send out email notifications on failed or successfully backup runs, provide SMTP credentials, a sender and a recipient:
```yaml ```yaml
services: services:
@@ -27,9 +27,13 @@ services:
- MAIL_PORT=587 - MAIL_PORT=587
- MAIL_USERNAME= - MAIL_USERNAME=
- MAIL_PASSWORD=! - MAIL_PASSWORD=!
- MAIL_FROM=sender@example.com - MAIL_FROM=
- MAIL_TO=me@example.com,team@example.com,manager@example.com - MAIL_TO=me@example.com,team@example.com,manager@example.com
- MAIL_SKIP_TLS=false - MAIL_SKIP_TLS=false
## Time format for notification
- TIME_FORMAT=2006-01-02 at 15:04:05
## Backup reference, in case you want to identifier every backup instance
- BACKUP_REFERENCE=database/Paris cluster
networks: networks:
- web - web
networks: networks:
@@ -54,6 +58,10 @@ services:
- DB_PASSWORD=password - DB_PASSWORD=password
- TG_TOKEN=[BOT ID]:[BOT TOKEN] - TG_TOKEN=[BOT ID]:[BOT TOKEN]
- TG_CHAT_ID= - TG_CHAT_ID=
## Time format for notification
- TIME_FORMAT=2006-01-02 at 15:04:05
## Backup reference, in case you want to identifier every backup instance
- BACKUP_REFERENCE=database/Paris cluster
networks: networks:
- web - web
networks: networks:
@@ -62,12 +70,13 @@ networks:
### Customize notifications ### Customize notifications
The body of the notifications can be tailored to your needs using Go templates. The title and body of the notifications can be tailored to your needs using Go templates.
Template sources must be mounted inside the container in /config/templates: Template sources must be mounted inside the container in /config/templates:
- email.template: Email notification template - email.template: Email notification template
- telegram.template: Telegram notification template - telegram.template: Telegram notification template
- error.template: Error notification template - email-error.template: Error notification template
- telegram-error.template: Error notification template
### Data ### Data
@@ -78,7 +87,7 @@ Here is a list of all data passed to the template:
- `Storage`: Backup storage - `Storage`: Backup storage
- `BackupLocation`: Backup location - `BackupLocation`: Backup location
- `BackupSize`: Backup size - `BackupSize`: Backup size
- `BackupReference`: Backup reference(eg: database/cluster name or server name)
> email.template: > email.template:
@@ -88,7 +97,7 @@ Here is a list of all data passed to the template:
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>[✅ Database Backup Notification {{.Database}}</title> <title>✅ Database Backup Notification {{.Database}}</title>
</head> </head>
<body> <body>
<h2>Hi,</h2> <h2>Hi,</h2>
@@ -101,6 +110,7 @@ Here is a list of all data passed to the template:
<li>Backup Storage: {{.Storage}}</li> <li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li> <li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li> <li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>Best regards,</p> <p>Best regards,</p>
</body> </body>
@@ -110,7 +120,7 @@ Here is a list of all data passed to the template:
> telegram.template > telegram.template
```html ```html
[✅ Database Backup Notification {{.Database}} ✅ Database Backup Notification {{.Database}}
Hi, Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}. Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
@@ -121,9 +131,32 @@ Backup Details:
- Backup Storage: {{.Storage}} - Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}} - Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes - Backup Size: {{.BackupSize}} bytes
- Backup Reference: {{.BackupReference}}
``` ```
> error.template > email-error.template
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🔴 Urgent: Database Backup Failure Notification</title>
</head>
<body>
<h2>Hi,</h2>
<p>An error occurred during database backup.</p>
<h3>Failure Details:</h3>
<ul>
<li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul>
</body>
</html>
```
> telegram-error.template
```html ```html

View File

@@ -218,7 +218,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
} }
func localBackup(db *dbConfig, config *BackupConfig) { func localBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to local storage") utils.Info("Backup database to local storage")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName finalFileName := config.backupFileName
if config.encryption { if config.encryption {
@@ -241,7 +241,7 @@ func localBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete old backup //Delete old backup
if config.prune { if config.prune {
@@ -256,7 +256,7 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME") bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH") s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH")
utils.Info("Backup database to s3 storage") utils.Info("Backup database to s3 storage")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -301,7 +301,7 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()
@@ -310,7 +310,7 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
} }
func sshBackup(db *dbConfig, config *BackupConfig) { func sshBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to Remote server") utils.Info("Backup database to Remote server")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -353,7 +353,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()
@@ -362,7 +362,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
} }
func ftpBackup(db *dbConfig, config *BackupConfig) { func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server") utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -405,7 +405,7 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()

View File

@@ -11,6 +11,7 @@
<ul> <ul>
<li>Error Message: {{.Error}}</li> <li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li> <li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p> <p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>
</body> </body>

View File

@@ -15,6 +15,7 @@
<li>Backup Storage: {{.Storage}}</li> <li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li> <li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li> <li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>Best regards,</p> <p>Best regards,</p>
<p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p> <p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>

View File

@@ -2,6 +2,7 @@
Hi, Hi,
An error occurred during database backup. An error occurred during database backup.
Failure Details: Failure Details:
Date: {{.EndTime}} - Date: {{.EndTime}}
Error Message: {{.Error}} - Backup Reference: {{.BackupReference}}
- Error Message: {{.Error}}

View File

@@ -1,4 +1,4 @@
✅ Database Backup Notification {{.Database}} [✅ Database Backup Notification {{.Database}}
Hi, Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}. Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
@@ -9,3 +9,4 @@ Backup Details:
- Backup Storage: {{.Storage}} - Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}} - Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes - Backup Size: {{.BackupSize}} bytes
- Backup Reference: {{.BackupReference}}

View File

@@ -19,13 +19,16 @@ type NotificationData struct {
EndTime string EndTime string
Storage string Storage string
BackupLocation string BackupLocation string
BackupReference string
} }
type ErrorMessage struct { type ErrorMessage struct {
Database string Database string
EndTime string EndTime string
Error string Error string
BackupReference string
} }
// loadMailConfig gets mail environment variables and returns MailConfig
func loadMailConfig() *MailConfig { func loadMailConfig() *MailConfig {
return &MailConfig{ return &MailConfig{
MailHost: os.Getenv("MAIL_HOST"), MailHost: os.Getenv("MAIL_HOST"),
@@ -39,4 +42,18 @@ func loadMailConfig() *MailConfig {
} }
// TimeFormat returns the format of the time
func TimeFormat() string {
format := os.Getenv("TIME_FORMAT")
if format == "" {
return "2006-01-02 at 15:04:05"
}
return format
}
func backupReference() string {
return os.Getenv("BACKUP_REFERENCE")
}
const templatePath = "/config/templates" const templatePath = "/config/templates"

View File

@@ -6,9 +6,9 @@
**/ **/
package utils package utils
const RestoreExample = "mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" + const RestoreExample = "restore --dbname database --file db_20231219_022941.sql.gz\n" +
"restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz" "restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
const BackupExample = "mysql-bkup backup --dbname database --disable-compression\n" + const BackupExample = "backup --dbname database --disable-compression\n" +
"backup --dbname database --storage s3 --path /custom-path --disable-compression" "backup --dbname database --storage s3 --path /custom-path --disable-compression"
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" + const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +

View File

@@ -31,8 +31,8 @@ func parseTemplate[T any](data T, fileName string) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func SendEmail(subject, body string) { func SendEmail(subject, body string) error {
Info("Start sending email....") Info("Start sending email notification....")
config := loadMailConfig() config := loadMailConfig()
emails := strings.Split(config.MailTo, ",") emails := strings.Split(config.MailTo, ",")
m := mail.NewMessage() m := mail.NewMessage()
@@ -44,14 +44,16 @@ func SendEmail(subject, body string) {
d.TLSConfig = &tls.Config{InsecureSkipVerify: config.SkipTls} d.TLSConfig = &tls.Config{InsecureSkipVerify: config.SkipTls}
if err := d.DialAndSend(m); err != nil { if err := d.DialAndSend(m); err != nil {
Fatal("Error could not send email : %v", err) Error("Error could not send email : %v", err)
return err
} }
Info("Email has been sent") Info("Email notification has been sent")
return nil
} }
func sendMessage(msg string) { func sendMessage(msg string) error {
Info("Sending notification... ") Info("Sending Telegram notification... ")
chatId := os.Getenv("TG_CHAT_ID") chatId := os.Getenv("TG_CHAT_ID")
body, _ := json.Marshal(map[string]string{ body, _ := json.Marshal(map[string]string{
"chat_id": chatId, "chat_id": chatId,
@@ -67,18 +69,21 @@ func sendMessage(msg string) {
client := &http.Client{} client := &http.Client{}
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
panic(err) return err
} }
code := response.StatusCode code := response.StatusCode
if code == 200 { if code == 200 {
Info("Notification has been sent") Info("Telegram notification has been sent")
return nil
} else { } else {
body, _ := ioutil.ReadAll(response.Body) body, _ := ioutil.ReadAll(response.Body)
Fatal("Error could not send message, error: %s", string(body)) Error("Error could not send message, error: %s", string(body))
return fmt.Errorf("error could not send message %s", string(body))
} }
} }
func NotifySuccess(notificationData *NotificationData) { func NotifySuccess(notificationData *NotificationData) {
notificationData.BackupReference = backupReference()
var vars = []string{ var vars = []string{
"TG_TOKEN", "TG_TOKEN",
"TG_CHAT_ID", "TG_CHAT_ID",
@@ -99,17 +104,23 @@ func NotifySuccess(notificationData *NotificationData) {
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse email template: %v", err)
} }
SendEmail(fmt.Sprintf("✅ Database Backup Notification %s", notificationData.Database), body) err = SendEmail(fmt.Sprintf("✅ Database Backup Notification %s", notificationData.Database), body)
if err != nil {
Error("Could not send email: %v", err)
}
} }
//Telegram notification //Telegram notification
err = CheckEnvVars(vars) err = CheckEnvVars(vars)
if err == nil { if err == nil {
message, err := parseTemplate(*notificationData, "telegram.template") message, err := parseTemplate(*notificationData, "telegram.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse telegram template: %v", err)
} }
sendMessage(message) err = sendMessage(message)
if err != nil {
Error("Could not send Telegram message: %v", err)
}
} }
} }
func NotifyError(error string) { func NotifyError(error string) {
@@ -131,25 +142,34 @@ func NotifyError(error string) {
if err == nil { if err == nil {
body, err := parseTemplate(ErrorMessage{ body, err := parseTemplate(ErrorMessage{
Error: error, Error: error,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
}, "email-error.template") }, "email-error.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse error template: %v", err)
}
err = SendEmail(fmt.Sprintf("🔴 Urgent: Database Backup Failure Notification"), body)
if err != nil {
Error("Could not send email: %v", err)
} }
SendEmail(fmt.Sprintf("🔴 Urgent: Database Backup Failure Notification"), body)
} }
//Telegram notification //Telegram notification
err = CheckEnvVars(vars) err = CheckEnvVars(vars)
if err == nil { if err == nil {
message, err := parseTemplate(ErrorMessage{ message, err := parseTemplate(ErrorMessage{
Error: error, Error: error,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
}, "telegram-error.template") }, "telegram-error.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse error template: %v", err)
} }
sendMessage(message) err = sendMessage(message)
if err != nil {
Error("Could not send telegram message: %v", err)
}
} }
} }