job_runs: add lifecycle.triggers.on_file_change - #6309
Conversation
Integration test reportCommit: 786a2d2
7 interesting tests: 4 SKIP, 2 flaky, 1 RECOVERED
Top 6 slowest tests (at least 2 minutes):
|
Approval status: pending
|
b8c20f6 to
8744e5e
Compare
Re-fire a run when matched file contents change, using stable hash fingerprints so mtime-only updates (touch) do not recreate.
Satisfy the linter on PrepareState literals after adding OnFileChange, and refresh out.fields.txt so validate-generated stays green.
Reject patterns outside the sync root and directory-only matches so the trigger cannot fingerprint escaped paths or silently disarm.
Drop size/mtime fingerprints and the resolve fast path so content identity alone drives recreate, and touch no longer needs a planner exception.
The modernize linter rejects the manual m[k]=v copy loop, failing CI lint.
8744e5e to
47994d8
Compare
| } | ||
| defer f.Close() | ||
|
|
||
| h := sha256.New() |
There was a problem hiding this comment.
This implementation is fine but it also does way too much potentially.
We could also do this:
- store stat(x).st_size and stat(x).st_mtime in the state in addition to hash
- check stat values first:
= st_size is different? can trigger update without calculating the hash
= st_size the same and t_mtime is the same? can skip hashing, file was not touched so hash can be assumed the same.
However, let's discuss this first, because we do something similar in file upload so I wonder if we should just re-use the mechanism there.
There was a problem hiding this comment.
I tried size+mtime in state in an earlier revision. touch then showed up as local drift, so we needed ignore_local_changes on mtime_ns to prevent it from recreating. Since mtime isn’t part of the recreate condition, putting it in planned state felt less elegant than just hashing the content. I also wouldn’t reuse file upload for this: sync is mtime-only, so a touch would trigger a re-upload and re-fire the job. What do you think?
There was a problem hiding this comment.
right, let's keep your implementation it's simple and you only pay for files you track. We can add local optimization later, maybe using cache that we have.
| "lifecycle": { | ||
| "action": "recreate", | ||
| "reason": "immutable", | ||
| "old": { |
There was a problem hiding this comment.
Could you look into why the change key is "lifecycle" and not a more detailed one like 'lifecycle.triggers.on_file_change["seed.txt"]'.
I supposed it is because remote is nil for the whole lifecycle? perhaps it can be hacked to have empty on_file_changes map?
There was a problem hiding this comment.
The leaf is already at lifecycle.triggers.on_file_change['seed.txt'], and the extra "lifecycle" key is the same change at the subtree root -- I think structdiff emits both
There was a problem hiding this comment.
we should not have both, it's not helpful there.
Honor .gitignore and sync.exclude when hashing matches. Cover mysubdir globs, READPLAN, and keep LF on Windows so restored files hash cleanly.
The glob test edits migrations/ignored.txt, but the test .gitignore excluded it from the commit so CI clones would miss the file.
Plan serialization is already covered by on_file_change; the glob cases only assert the hash map.
Cover deploying glob hashes from a saved plan, not only from a live re-plan.
| "lifecycle": { | ||
| "action": "recreate", | ||
| "reason": "immutable", | ||
| "old": { |
There was a problem hiding this comment.
we should not have both, it's not helpful there.
| "mysubdir/a.txt": "[FILE_HASH][2]" | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
try initializing remote object with {"lifecycle": {triggers: on_file_change: {}}, I think that can help to get rid of this block.
There was a problem hiding this comment.
or maybe we need to fix diffing in general to recognize such cases.
| @@ -0,0 +1,212 @@ | |||
| package mutator_test | |||
|
|
|||
| import ( | |||
There was a problem hiding this comment.
do these tests add anything on top of acc tests?
| } | ||
| } | ||
| if len(input.ResolvedFileTriggers) > 0 { | ||
| if triggers == nil { |
There was a problem hiding this comment.
do we need all this conditions? would it be bad tojust create struct unconditionally?
Changes
Add
lifecycle.triggers.on_file_changeforresources.job_runs(direct engine).touchdoes not re-fire)..gitignoreandsync.exclude).prevent_destroy.Why
Users need to re-run a job when local inputs change (SQL migrations, notebooks, configs) without re-firing on every deploy. Content hashes keep the trigger stable under sync noise while still catching real edits.
Tests
sync.exclude), validate,PrepareState/OverrideChangeDesc.on_file_change— deploy, unchanged redeploy, touch, edit → recreate (including READPLAN), delete/restore file, clear trigger → update-only.on_file_change_glob—migrations/*plusmysubdir/*.txt; appear/disappear; nested dir and gitignored files do not re-fire;**is not recursive; READPLAN.on_file_change_errors— path outside sync root, directory-only glob, no-match warning.