snapshotting: ability to specify timestamp location != UTC (#801)

This PR adds a new field optional field `timestamp_location` that allows
the user to specify a timezone different than the default UTC for use in
the snapshot suffix.

I took @mjasnik 's PR https://github.com/zrepl/zrepl/pull/785 and
refactored+extended it as follows:
* move all formatting logic into its own package
* disallow `dense` and `human` with formats != UTC to protect users from
stupidity
* document behavior more clearly
* regression test for existing users
This commit is contained in:
Christian Schwarz
2024-10-18 15:12:41 +02:00
committed by GitHub
parent 904c1512a3
commit b9b9ad10cf
11 changed files with 322 additions and 66 deletions
+11 -9
View File
@@ -55,22 +55,22 @@ var componentValidChar = regexp.MustCompile(`^[0-9a-zA-Z-_\.: ]+$`)
// [-_.: ]
func ComponentNamecheck(datasetPathComponent string) error {
if len(datasetPathComponent) == 0 {
return fmt.Errorf("path component must not be empty")
return fmt.Errorf("must not be empty")
}
if len(datasetPathComponent) > MaxDatasetNameLen {
return fmt.Errorf("path component must not be longer than %d chars", MaxDatasetNameLen)
return fmt.Errorf("must not be longer than %d chars", MaxDatasetNameLen)
}
if !(isASCII(datasetPathComponent)) {
return fmt.Errorf("path component must be ASCII")
return fmt.Errorf("must be ASCII")
}
if !componentValidChar.MatchString(datasetPathComponent) {
return fmt.Errorf("path component must only contain alphanumeric chars and any in %q", "-_.: ")
return fmt.Errorf("must only contain alphanumeric chars and any in %q", "-_.: ")
}
if datasetPathComponent == "." || datasetPathComponent == ".." {
return fmt.Errorf("path component must not be '%s'", datasetPathComponent)
return fmt.Errorf("must not be '%s'", datasetPathComponent)
}
return nil
@@ -155,8 +155,9 @@ func EntityNamecheck(path string, t EntityType) (err *PathValidationError) {
if bookmarkOrSnapshotDelims == 0 {
// hot path, all but last component
if err := ComponentNamecheck(string(comp)); err != nil {
return pve(err.Error())
component := string(comp)
if err := ComponentNamecheck(component); err != nil {
return pve(fmt.Sprintf("component %q: %s", component, err.Error()))
}
continue
}
@@ -172,8 +173,9 @@ func EntityNamecheck(path string, t EntityType) (err *PathValidationError) {
}
for _, comp := range subComps {
if err := ComponentNamecheck(string(comp)); err != nil {
return pve(err.Error())
component := string(comp)
if err := ComponentNamecheck(component); err != nil {
return pve(fmt.Sprintf("component %q: %s", component, err.Error()))
}
}
+4
View File
@@ -49,6 +49,10 @@ func TestEntityNamecheck(t *testing.T) {
{strings.Repeat("a", MaxDatasetNameLen-2) + "/a", EntityTypeFilesystem, true},
{strings.Repeat("a", MaxDatasetNameLen-4) + "/a@b", EntityTypeSnapshot, true},
{strings.Repeat("a", MaxDatasetNameLen) + "/a@b", EntityTypeSnapshot, false},
// + is not allowed, and particularly relevant to test here because
// common timestamp formats usually use `+` as a delimiter for numeric timezone offset
// => cf with package `timestamp_formatting`
{"foo/bar@23+42", EntityTypeSnapshot, false},
}
for idx := range tcs {