1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
package main
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/adhocore/gronx"
localUtil "github.com/lxc/incus/v6/internal/server/util"
"github.com/lxc/incus/v6/shared/util"
)
// SnapshotScheduleAliases contains the mapping of scheduling aliases to cron syntax
// including placeholders for scheduled time obfuscation.
var SnapshotScheduleAliases = map[string]string{
"@hourly": "%s * * * *",
"@daily": "%s %s * * *",
"@midnight": "%s 0 * * *",
"@weekly": "%s %s * * 0",
"@monthly": "%s %s 1 * *",
"@annually": "%s %s 1 1 *",
"@yearly": "%s %s 1 1 *",
"@never": "",
}
func snapshotIsScheduledNow(spec string, subjectID int64) bool {
result := false
specs := buildCronSpecs(spec, subjectID)
for _, curSpec := range specs {
isNow, err := cronSpecIsNow(curSpec)
if err == nil && isNow {
result = true
}
}
return result
}
func buildCronSpecs(spec string, subjectID int64) []string {
var result []string
if strings.Contains(spec, ", ") {
for _, curSpec := range util.SplitNTrimSpace(spec, ",", -1, true) {
entry := getCronSyntax(curSpec, subjectID)
if entry != "" {
result = append(result, entry)
}
}
} else {
entry := getCronSyntax(spec, subjectID)
if entry != "" {
result = append(result, entry)
}
}
return result
}
func getCronSyntax(spec string, subjectID int64) string {
alias, isAlias := SnapshotScheduleAliases[strings.ToLower(spec)]
if isAlias {
if alias == "@never" {
return ""
}
obfuscatedMinute, obfuscatedHour := getObfuscatedTimeValuesForSubject(subjectID)
if strings.Count(alias, "%s") > 1 {
return fmt.Sprintf(alias, obfuscatedMinute, obfuscatedHour)
} else {
return fmt.Sprintf(alias, obfuscatedMinute)
}
}
return spec
}
func getObfuscatedTimeValuesForSubject(subjectID int64) (string, string) {
minuteResult := "0"
hourResult := "0"
minSequence, minSequenceErr := localUtil.GenerateSequenceInt64(0, 60, 1)
min, minErr := localUtil.GetStableRandomInt64FromList(subjectID, minSequence)
if minErr == nil && minSequenceErr == nil {
minuteResult = strconv.FormatInt(min, 10)
}
hourSequence, hourSequenceErr := localUtil.GenerateSequenceInt64(0, 24, 1)
hour, hourErr := localUtil.GetStableRandomInt64FromList(subjectID, hourSequence)
if hourErr == nil && hourSequenceErr == nil {
hourResult = strconv.FormatInt(hour, 10)
}
return minuteResult, hourResult
}
func cronSpecIsNow(spec string) (bool, error) {
// Check if it's time to snapshot
now := time.Now()
// Truncate the time now back to the start of the minute.
// This is needed because the cron scheduler will add a minute to the scheduled time
// and we don't want the next scheduled time to roll over to the next minute and break
// the time comparison below.
now = now.Truncate(time.Minute)
// Calculate the next scheduled time based on the snapshots.schedule
// pattern and the time now.
next, err := gronx.NextTickAfter(spec, now, false)
if err != nil {
return false, fmt.Errorf("Could not parse cron '%s': %w", spec, err)
}
if !now.Add(time.Minute).Equal(next) {
return false, nil
}
return true, nil
}
|