feat: improve display report
This commit is contained in:
parent
961b68bf4e
commit
f43fae8c3d
69
cmd/check.go
69
cmd/check.go
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue