Skip to content

Commit 0b5dc2d

Browse files
authored
Merge pull request #16 from grove-platform/DOCSP-58752
Add backup protection
2 parents 84781a0 + ed4c4df commit 0b5dc2d

1 file changed

Lines changed: 72 additions & 46 deletions

File tree

audit/gdcd/db/BackUpDB.go

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ func BackUpDb() {
5151
log.Fatalf("Error listing collections: %v", err)
5252
}
5353

54+
// Check if a same-day or prior-day backup already exists. If so, skip the copy.
55+
// This prevents overwriting the current backup when re-running a job after a partial run.
56+
existingBackups := getBackupDbNames(client, ctx)
57+
if hasRecentBackup(existingBackups) {
58+
log.Println("A same-day or prior-day backup already exists. Skipping backup copy.")
59+
return
60+
}
61+
5462
log.Println("Backing up database...")
5563
// Iterate over each collection, copying records from the source to target DB
5664
for _, collName := range collectionNames {
@@ -86,18 +94,73 @@ func BackUpDb() {
8694
}
8795
log.Println("Successfully backed up database")
8896

89-
// Drop the oldest backup. Get a list of backup names so we can find the oldest backup.
97+
// Drop the oldest backup only if we have at least 3 backups AND the oldest is at least 21 days
98+
// old. This ensures we maintain ~3 weeks of weekly history and prevents accidentally dropping
99+
// all backups during partial runs or tightly-clustered re-runs.
90100
backupNames := getBackupDbNames(client, ctx)
91-
oldestBackup := findOldestBackup(backupNames)
92-
// Get a handle for the database
93-
dbToDrop := client.Database(oldestBackup)
94-
95-
// Drop the database
101+
const minBackupCount = 3
102+
const minBackupAgeDays = 21
103+
if len(backupNames) < minBackupCount {
104+
log.Printf("Only %d backup(s) exist (need %d). Skipping oldest backup drop.", len(backupNames), minBackupCount)
105+
return
106+
}
107+
oldestBackupDate, err := parseBackupDate(findOldestBackup(backupNames))
108+
if err != nil {
109+
log.Fatalf("Failed to parse oldest backup date: %v", err)
110+
}
111+
oldestAgedays := int(time.Since(oldestBackupDate).Hours() / 24)
112+
if oldestAgedays < minBackupAgeDays {
113+
log.Printf("Oldest backup is only %d day(s) old (need %d). Skipping oldest backup drop.", oldestAgedays, minBackupAgeDays)
114+
return
115+
}
116+
oldestBackupName := findOldestBackup(backupNames)
117+
dbToDrop := client.Database(oldestBackupName)
96118
err = dbToDrop.Drop(ctx)
97119
if err != nil {
98-
log.Fatalf("Failed to drop database %v: %v", oldestBackup, err)
120+
log.Fatalf("Failed to drop database %v: %v", oldestBackupName, err)
121+
}
122+
log.Printf("Oldest backup database '%s' dropped successfully\n", oldestBackupName)
123+
}
124+
125+
// hasRecentBackup returns true if any backup is from today or yesterday.
126+
func hasRecentBackup(backupNames []string) bool {
127+
now := time.Now().UTC()
128+
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
129+
yesterday := today.AddDate(0, 0, -1)
130+
for _, name := range backupNames {
131+
date, err := parseBackupDate(name)
132+
if err != nil {
133+
continue
134+
}
135+
if !date.Before(yesterday) {
136+
return true
137+
}
99138
}
100-
log.Printf("Oldest backup database '%s' dropped successfully\n", oldestBackup)
139+
return false
140+
}
141+
142+
// parseBackupDate extracts the date from a backup database name (e.g. "backup_code_metrics_March_26").
143+
func parseBackupDate(name string) (time.Time, error) {
144+
parts := strings.Split(name, "_")
145+
if len(parts) < 4 {
146+
return time.Time{}, fmt.Errorf("invalid backup name: %s", name)
147+
}
148+
monthStr := parts[len(parts)-2]
149+
dayStr := parts[len(parts)-1]
150+
day, err := strconv.Atoi(dayStr)
151+
if err != nil {
152+
return time.Time{}, fmt.Errorf("invalid day in backup name %s: %v", name, err)
153+
}
154+
month, err := parseMonth(monthStr)
155+
if err != nil {
156+
return time.Time{}, err
157+
}
158+
now := time.Now().UTC()
159+
year := now.Year()
160+
if month > now.Month() {
161+
year--
162+
}
163+
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil
101164
}
102165

103166
// The cluster contains a mix of databases - some are backups, and some are other databases.
@@ -120,50 +183,13 @@ func getBackupDbNames(client *mongo.Client, ctx context.Context) []string {
120183

121184
// Parse the dates from the backup database names to find the oldest backup database.
122185
func findOldestBackup(backupNames []string) string {
123-
// Get the current date to determine the year for each backup
124-
now := time.Now()
125-
currentMonth := now.Month()
126-
currentYear := now.Year()
127-
128-
// Variables to track the oldest date and its corresponding string
129186
var oldestDate time.Time
130187
var oldestBackupName string
131-
132-
// Iterate over the strings to extract and compare dates
133188
for _, entry := range backupNames {
134-
// Split the string and find the month and day (assume the format is fixed)
135-
parts := strings.Split(entry, "_")
136-
if len(parts) < 4 {
137-
continue // Skip invalid strings
138-
}
139-
monthStr := parts[len(parts)-2] // Second-to-last part is the month
140-
dayStr := parts[len(parts)-1] // Last part is the day
141-
142-
// Convert the day string to an integer
143-
day, err := strconv.Atoi(dayStr)
189+
date, err := parseBackupDate(entry)
144190
if err != nil {
145-
fmt.Println("Error converting day:", err)
146191
continue
147192
}
148-
149-
// Parse the month using the time.Month enum
150-
month, err := parseMonth(monthStr)
151-
if err != nil {
152-
fmt.Println("Error parsing month:", err)
153-
continue
154-
}
155-
156-
// Determine the year for this backup
157-
// If the backup month is after the current month, it must be from the previous year
158-
year := currentYear
159-
if month > currentMonth {
160-
year = currentYear - 1
161-
}
162-
163-
// Create a time.Time object for the given month and day
164-
date := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
165-
166-
// Compare dates to find the earliest one
167193
if oldestBackupName == "" || date.Before(oldestDate) {
168194
oldestDate = date
169195
oldestBackupName = entry

0 commit comments

Comments
 (0)