Preflight comparison transaction names

This commit is contained in:
2026-08-13 03:09:00 +00:00
parent 707db5394c
commit 0314a302f1
4 changed files with 78 additions and 2 deletions

View File

@@ -24,6 +24,11 @@ const (
DestinationNotEmpty DestinationErrorKind = "not_empty"
DestinationUnrecognized DestinationErrorKind = "unrecognized"
DestinationInspection DestinationErrorKind = "inspection"
maxComparisonComponentBytes = 255
temporaryRandomBytes = 10
stagingDirectoryPattern = ".weatherreporter-staging-*"
backupDirectoryPattern = ".weatherreporter-backup-*"
)
// DestinationError provides inspectable context without making filesystem
@@ -82,6 +87,9 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
if target == workingDirectory {
return DestinationPlan{}, newDestinationError(DestinationWorkingDirectory, target, nil)
}
if err := validateTransactionSiblingNames(target); err != nil {
return DestinationPlan{}, newDestinationError(DestinationInvalidPath, target, err)
}
plan := DestinationPlan{WorkingDirectory: workingDirectory, Target: target, Replace: replace}
info, err := os.Lstat(target)
@@ -119,6 +127,22 @@ func PlanDestination(workingDirectory, target string, replace bool) (Destination
return plan, nil
}
func validateTransactionSiblingNames(target string) error {
if len(filepath.Base(target)) > maxComparisonComponentBytes {
return fmt.Errorf("destination name exceeds the %d-byte limit", maxComparisonComponentBytes)
}
for _, pattern := range []string{stagingDirectoryPattern, backupDirectoryPattern} {
if !temporaryPatternFitsComponentLimit(pattern) {
return fmt.Errorf("comparison transaction sibling pattern exceeds the %d-byte limit", maxComparisonComponentBytes)
}
}
return nil
}
func temporaryPatternFitsComponentLimit(pattern string) bool {
return len(strings.Replace(pattern, "*", strings.Repeat("0", temporaryRandomBytes), 1)) <= maxComparisonComponentBytes
}
func absoluteCleanPath(value string) (string, error) {
if strings.TrimSpace(value) == "" {
return "", fmt.Errorf("path is required")
@@ -480,7 +504,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
if err := os.MkdirAll(filepath.Dir(plan.Target), 0o755); err != nil {
return PublicationResult{}, fmt.Errorf("create comparison destination parent %q: %w", filepath.Dir(plan.Target), err)
}
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(plan.Target), "."+filepath.Base(plan.Target)+".staging-")
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(plan.Target), stagingDirectoryPattern)
if err != nil {
return PublicationResult{}, fmt.Errorf("create comparison staging directory: %w", err)
}
@@ -516,7 +540,7 @@ func publish(ctx context.Context, plan DestinationPlan, bundle LogicalBundle, op
return PublicationResult{Committed: true}, nil
}
backupDirectory, err := uniqueSiblingPath(filepath.Dir(currentPlan.Target), "."+filepath.Base(currentPlan.Target)+".backup-")
backupDirectory, err := uniqueSiblingPath(filepath.Dir(currentPlan.Target), backupDirectoryPattern)
if err != nil {
return PublicationResult{}, err
}