feat: improve display report

This commit is contained in:
Urko 2023-08-28 11:01:57 +02:00
parent 961b68bf4e
commit f43fae8c3d
4 changed files with 69 additions and 47 deletions

View File

@ -8,6 +8,7 @@ import (
"log" "log"
"net/smtp" "net/smtp"
"os" "os"
"strings"
"time" "time"
"gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze" "gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze"
@ -71,16 +72,20 @@ var Check = &cobra.Command{
logger.Info("start check") logger.Info("start check")
defer logger.Info("end check") defer logger.Info("end check")
errs := make(chan backblaze.B2LocalErr) msgChan := make(chan backblaze.B2Local)
go b.CompareConcurrent(ctx, backupDir, bucketName, errs) go b.CompareConcurrent(ctx, backupDir, bucketName, msgChan)
// Create two buffers for two kinds of errors reportBuilder := strings.Builder{}
cloudNotInLocalBuffer := new(bytes.Buffer) countLocalFiles := 0
cloudNotInLocalBuffer.WriteString(fmt.Sprintf("List of B2 files within %s bucket not found in local path %s \n", bucketName, backupDir)) countOK := 0
reportBuilder.WriteString(fmt.Sprintf("Local files within `%s` path already in `%s` bucket:\n", backupDir, bucketName))
cloudBuilder := strings.Builder{}
cloudBuilder.WriteString(fmt.Sprintf("List of B2 files within `%s` bucket not found in local path `%s`\n", bucketName, backupDir))
countNotInLocalBuffer := 0 countNotInLocalBuffer := 0
localNotInCloudBuffer := new(bytes.Buffer) localBuilder := strings.Builder{}
localNotInCloudBuffer.WriteString(fmt.Sprintf("List of local files in %s not found in B2 bucket %s \n", backupDir, bucketName)) localBuilder.WriteString(fmt.Sprintf("List of local files in `%s` not found in B2 bucket `%s`\n", backupDir, bucketName))
countNotInCloudBuffer := 0 countNotInCloudBuffer := 0
loop: loop:
@ -95,38 +100,46 @@ var Check = &cobra.Command{
logger.Error("Operation canceled") logger.Error("Operation canceled")
} }
break loop break loop
case err := <-errs: case msg := <-msgChan:
if errors.Is(err.Err, backblaze.ErrCloudNotInLocal) { if msg.Err == nil && msg.File == "" {
logger.Debug(err.File + ": B2 file not found in local") countLocalFiles = msg.LocalCount
cloudNotInLocalBuffer.WriteString(err.File + "\n")
countNotInLocalBuffer++
}
if errors.Is(err.Err, backblaze.ErrLocalNotInCloud) {
logger.Debug(err.File + ": local file not found in B2")
localNotInCloudBuffer.WriteString(err.File + "\n")
countNotInCloudBuffer++
}
if err.Err == nil {
break loop break loop
} }
if errors.Is(msg.Err, backblaze.ErrCloudNotInLocal) {
logger.Debug(msg.File + ": B2 file not found in local")
cloudBuilder.WriteString(msg.File + "\n")
countNotInLocalBuffer++
continue
}
if errors.Is(msg.Err, backblaze.ErrLocalNotInCloud) {
logger.Debug(msg.File + ": local file not found in B2")
localBuilder.WriteString(msg.File + "\n")
countNotInCloudBuffer++
continue
}
reportBuilder.WriteString(msg.File + " OK" + "\n")
countOK++
} }
} }
var attachments []email.EmailAttachment
if countNotInCloudBuffer > 0 { if countNotInCloudBuffer > 0 {
attachments = append(attachments, email.EmailAttachment{File: cloudNotInLocalBuffer, Title: "B2-Files-Not-In-Local.txt"}) reportBuilder.WriteString("\n")
reportBuilder.WriteString(cloudBuilder.String())
} }
if countNotInLocalBuffer > 0 { if countNotInLocalBuffer > 0 {
attachments = append(attachments, email.EmailAttachment{File: localNotInCloudBuffer, Title: "Local-Files-Not-In-B2.txt"}) reportBuilder.WriteString("\n")
reportBuilder.WriteString(localBuilder.String())
} }
if err := mailSrv.SendOK(email.EmailWithAttachments{ if err := mailSrv.SendOK(email.EmailWithAttachments{
To: cfg.MailTo, To: cfg.MailTo,
Bucket: bucketName, Bucket: bucketName,
BackupDir: backupDir, BackupDir: backupDir,
CountLocal: countNotInLocalBuffer, CountLocal: countNotInLocalBuffer,
CountCloud: countNotInCloudBuffer, CountCloud: countNotInCloudBuffer,
Attachments: attachments, CountOK: countOK,
CountLocalFiles: countLocalFiles,
Attachments: []email.EmailAttachment{{File: bytes.NewReader([]byte(reportBuilder.String())), Title: fmt.Sprintf("%s-check-report.txt", bucketName)}},
}); err != nil { }); err != nil {
panic(fmt.Errorf("error while send email: %w", err)) panic(fmt.Errorf("error while send email: %w", err))
} }

View File

@ -66,15 +66,16 @@ func (b *BackBlaze) b2BucketFiles(ctx context.Context, bucketName string, fileCh
var ErrLocalNotInCloud error = errors.New("exists locally but not in the cloud") var ErrLocalNotInCloud error = errors.New("exists locally but not in the cloud")
var ErrCloudNotInLocal error = errors.New("exists on cloud but not locally") var ErrCloudNotInLocal error = errors.New("exists on cloud but not locally")
type B2LocalErr struct { type B2Local struct {
File string File string
Err error Err error
LocalCount int
} }
// CompareConcurrent concurrently fetches the list of local files and cloud files, // CompareConcurrent concurrently fetches the list of local files and cloud files,
// then compares them to ensure all local files exist in the cloud. // then compares them to ensure all local files exist in the cloud.
// Errors are sent to a provided error channel. The function will panic if an error occurs while listing files. // Errors are sent to a provided error channel. The function will panic if an error occurs while listing files.
func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName string, errors chan<- B2LocalErr) { func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName string, msgChan chan<- B2Local) {
var wg sync.WaitGroup var wg sync.WaitGroup
localFiles := make(map[string]int) localFiles := make(map[string]int)
cloudFiles := make(map[string]int) cloudFiles := make(map[string]int)
@ -92,7 +93,7 @@ func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName
if _, ok := localFiles[f]; ok { if _, ok := localFiles[f]; ok {
panic(fmt.Errorf("local file already exists in map: %s", f)) panic(fmt.Errorf("local file already exists in map: %s", f))
} }
b.logger.Debug("local file", f) b.logger.Debug("local file ", f)
localFiles[f]++ localFiles[f]++
} }
}() }()
@ -113,7 +114,7 @@ func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName
if _, ok := cloudFiles[f]; ok { if _, ok := cloudFiles[f]; ok {
panic(fmt.Errorf("cloud file already exists in map: %s", f)) panic(fmt.Errorf("cloud file already exists in map: %s", f))
} }
b.logger.Debug("B2 file", f) b.logger.Debug("B2 file ", f)
cloudFiles[f]++ cloudFiles[f]++
} }
}() }()
@ -125,14 +126,16 @@ func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName
// Wait for both to complete // Wait for both to complete
wg.Wait() wg.Wait()
// Now check local files that are not presesnt in cloud // Now check local files that are not present in cloud
wg.Add(2) wg.Add(2)
go func() { go func() {
defer wg.Done() defer wg.Done()
for localFile := range localFiles { for localFile := range localFiles {
if _, exists := cloudFiles[localFile]; !exists { if _, exists := cloudFiles[localFile]; !exists {
errors <- B2LocalErr{File: localFile, Err: ErrLocalNotInCloud} msgChan <- B2Local{File: localFile, Err: ErrLocalNotInCloud}
continue
} }
msgChan <- B2Local{File: localFile, Err: nil}
} }
}() }()
@ -141,12 +144,12 @@ func (b *BackBlaze) CompareConcurrent(ctx context.Context, backupDir, bucketName
defer wg.Done() defer wg.Done()
for cloudFile := range cloudFiles { for cloudFile := range cloudFiles {
if _, exists := localFiles[cloudFile]; !exists { if _, exists := localFiles[cloudFile]; !exists {
errors <- B2LocalErr{File: cloudFile, Err: ErrCloudNotInLocal} msgChan <- B2Local{File: cloudFile, Err: ErrCloudNotInLocal}
} }
} }
}() }()
wg.Wait() wg.Wait()
errors <- B2LocalErr{Err: nil} msgChan <- B2Local{Err: nil, LocalCount: len(localFiles)}
close(errors) close(msgChan)
} }

View File

@ -29,12 +29,14 @@ type EmailService struct {
} }
type EmailWithAttachments struct { type EmailWithAttachments struct {
To string To string
Bucket string Bucket string
BackupDir string BackupDir string
CountLocal int CountLocal int
CountCloud int CountOK int
Attachments []EmailAttachment CountCloud int
CountLocalFiles int
Attachments []EmailAttachment
} }
type EmailAttachment struct { type EmailAttachment struct {
@ -75,6 +77,8 @@ func (e *EmailService) SendOK(emailData EmailWithAttachments) error {
template = strings.Replace(template, "{{local_backup_path}}", emailData.BackupDir, -1) template = strings.Replace(template, "{{local_backup_path}}", emailData.BackupDir, -1)
template = strings.Replace(template, "{{count_ErrCloudNotInLocal}}", fmt.Sprint(emailData.CountCloud), -1) template = strings.Replace(template, "{{count_ErrCloudNotInLocal}}", fmt.Sprint(emailData.CountCloud), -1)
template = strings.Replace(template, "{{count_ErrLocalNotInCloud}}", fmt.Sprint(emailData.CountLocal), -1) template = strings.Replace(template, "{{count_ErrLocalNotInCloud}}", fmt.Sprint(emailData.CountLocal), -1)
template = strings.Replace(template, "{{count_ok}}", fmt.Sprint(emailData.CountOK), -1)
template = strings.Replace(template, "{{count_local}}", fmt.Sprint(emailData.CountLocalFiles), -1)
msg, err := newMessage(e.from, emailData.To, subjectReport). msg, err := newMessage(e.from, emailData.To, subjectReport).
withAttachments(template, emailData.Attachments) withAttachments(template, emailData.Attachments)

View File

@ -11,9 +11,11 @@ const htmlTemplate = `<!DOCTYPE html>
<h2 style="color: #333333; margin-top: 0;">Bucket Report</h2> <h2 style="color: #333333; margin-top: 0;">Bucket Report</h2>
<hr style="border: none; border-bottom: 1px solid #ddd;"> <hr style="border: none; border-bottom: 1px solid #ddd;">
<p><strong>Local Backup Path:</strong> {{local_backup_path}}</p> <p><strong>Local Backup Path:</strong> {{local_backup_path}}</p>
<p><strong>Local files:</strong> {{count_local}}</p>
<p><strong>Bucket Name:</strong> {{bucket}}</p> <p><strong>Bucket Name:</strong> {{bucket}}</p>
<p><strong>Count of ErrCloudNotInLocal:</strong> {{count_ErrCloudNotInLocal}}</p> <p><strong>OK local in B2:</strong> {{count_ok}}</p>
<p><strong>Count of ErrLocalNotInCloud:</strong> {{count_ErrLocalNotInCloud}}</p> <p><strong>B2 files not in local:</strong> {{count_ErrCloudNotInLocal}}</p>
<p><strong>Local files not in B2:</strong> {{count_ErrLocalNotInCloud}}</p>
<hr style="border: none; border-bottom: 1px solid #ddd;"> <hr style="border: none; border-bottom: 1px solid #ddd;">
<p style="text-align: center; color: #666666;">This is an automated report, please do not reply to this email.</p> <p style="text-align: center; color: #666666;">This is an automated report, please do not reply to this email.</p>
</div> </div>