diff --git a/integration/reload.bats b/integration/reload.bats index e8219d7..5110199 100755 --- a/integration/reload.bats +++ b/integration/reload.bats @@ -85,3 +85,40 @@ grep_test_file() { kill -s TERM "$PID" wait } + +@test "if inotify is enabled it handles RENAME events (vim-style atomic writes)" { + echo '* * * * * * * echo a > "$TEST_FILE"' > "$CRONTAB_FILE" + + "${BATS_TEST_DIRNAME}/../supercronic" -inotify "$CRONTAB_FILE" 3>&- & + PID="$!" + + wait_for grep_test_file a + + # Simulate vim-style atomic write: create temp file, write to it, rename over original + TEMP_FILE="${CRONTAB_FILE}.tmp" + echo '* * * * * * * echo b > "$TEST_FILE"' > "$TEMP_FILE" + mv "$TEMP_FILE" "$CRONTAB_FILE" + + wait_for grep_test_file b + + kill -s TERM "$PID" + wait +} + +@test "if inotify is enabled it handles CREATE events (file recreation)" { + echo '* * * * * * * echo a > "$TEST_FILE"' > "$CRONTAB_FILE" + + "${BATS_TEST_DIRNAME}/../supercronic" -inotify "$CRONTAB_FILE" 3>&- & + PID="$!" + + wait_for grep_test_file a + + # Simulate Create event: remove file, then create new one at same path + rm "$CRONTAB_FILE" + echo '* * * * * * * echo b > "$TEST_FILE"' > "$CRONTAB_FILE" + + wait_for grep_test_file b + + kill -s TERM "$PID" + wait +} diff --git a/main.go b/main.go index 8c6de95..094ac1c 100644 --- a/main.go +++ b/main.go @@ -202,19 +202,34 @@ func main() { } logrus.Debugf("event: %v, watch-list: %v", event, watcher.WatchList()) - switch event.Op { - case event.Op & fsnotify.Write: - logrus.Debug("watched file changed") + // Handle any file modification event (Write, Create, Remove, Rename) + // Many editors use atomic writes that generate Rename or Create events + if event.Op&fsnotify.Write != 0 { + logrus.Debug("watched file changed (write)") termChan <- syscall.SIGUSR2 - - // workaround for k8s configmap and secret mounts - case event.Op & fsnotify.Remove: - logrus.Debug("watched file changed") + // workaround for k8s configmap and secret mounts + } else if event.Op&fsnotify.Create != 0 { + logrus.Debug("watched file changed (create)") + termChan <- syscall.SIGUSR2 + } else if event.Op&fsnotify.Rename != 0 { + logrus.Debug("watched file changed (rename)") + // Re-add watch after rename (file might have been replaced) if err := watcher.Add(crontabFileName); err != nil { + logrus.Errorf("failed to re-add watch after rename: %s", err) logrus.Fatal(err) return + } else { + termChan <- syscall.SIGUSR2 + } + } else if event.Op&fsnotify.Remove != 0 { + // workaround for k8s configmap and secret mounts + logrus.Debug("watched file changed (remove)") + if err := watcher.Add(crontabFileName); err != nil { + logrus.Errorf("failed to re-add watch: %s", err) + // Don't return/fatal here - just log and continue + } else { + termChan <- syscall.SIGUSR2 } - termChan <- syscall.SIGUSR2 } case err, ok := <-watcher.Errors: