Added --simulate-json option to write the simulated change set as JSON - #6294
Added --simulate-json option to write the simulated change set as JSON#6294djbclark wants to merge 1 commit into
Conversation
f5ce3a3 to
05e18f0
Compare
|
Apologies for the force-push churn on this branch — the commit metadata is now correct, and this is what moved and why. The commit originally carried So No code changed in either direction. The tree is byte-identical to what you have already seen; both rewrites touched only the commit message, and I verified the tree hashes match before pushing. Still one commit. |
|
Please hold off merging this one too — the same review found defects here, and a correction is coming. Nothing as severe as the overflow in #6293, but enough that it should not go in as written. 1. A functional bug that also affects the existing prose output. 2. The JSON is not safely machine-readable for non-ASCII filenames. Filenames are emitted through a per-byte 3. High UIDs are reported as negative. 4. A failed JSON write does not fail the run. 5. 6. One claim in the commit text is too strong as written. "The prose renderers are unchanged" is right at file level — I had that measured independently and it holds — but Also housekeeping: I'll follow up with a force-push carrying the fixes and tests. |
|
Now tracked in Jira as CFE-4716. Same as #6293: we couldn't reach the CFE tracker when this PR went up, which is why the commit trailer points at discussion #6296. That's now resolved (thanks to @nickanderson — the Atlassian account alone wasn't enough, I also needed permission in the project). The Going forward all our CFEngine/libntech reports will go to Jira rather than email or new discussions. |
The changes computed by a --simulate run could only be rendered as prose meant for human eyes: the manifest and diff renderers print free-form text, and the record files in the changes chroot are an internal, unstable format. Any program consuming the simulated change set had to parse text that can be reworded at any time. With --simulate-json=FILE, cf-agent also writes the change set to FILE as a single JSON document: which files would be created, modified or deleted (with the type, permissions, ownership, size and SHA-256 content digest they would have after the run), which files would be renamed, and which packages would be installed or removed. The document carries a format_version field so that consumers can detect future changes to its structure. The option requires --simulate and an absolute path. The prose renderers remain the default output and their file-level output is unchanged. The package-operation renderers are not: the diff renderer now shares its reduction with the JSON output (see below), and both the diff and manifest renderers now correctly cancel a previously recorded removal when a later installation of the same package is recorded. Before, such a sequence was reported as both operations, because the cancellation was applied to a key that had already been handed over to the map. The reduction of the recorded package operations to the net set of install and remove operations is shared with the --simulate=diff prose renderer: DiffPkgOperations() now renders its messages from the reduced records at printing time instead of storing pre-rendered messages in them. Path names are written as raw UTF-8 bytes wherever they form valid UTF-8. JsonWrite() escapes every non-ASCII byte as an individual "\u00XX" sequence, which is well-formed JSON but denotes the code point U+00XX, so a conformant parser decodes each byte of a multi-byte character as a separate wrong character -- a file name like "café" would not survive the round trip. Bytes that are not part of a valid UTF-8 sequence stay escaped, since a JSON document has to be valid UTF-8 itself and there is no exact representation for them. The underlying escaping is libntech's and would be better fixed there; this is deliberately kept local to the change set writer so that the output of every other JsonWrite() caller stays as it is. Ownership is written as a 64-bit integer, so that a uid or gid that does not fit in an int -- 4294967294, the usual "nobody" on Linux -- is not reported as -2. The document is written to a temporary file created with O_EXCL, which is then renamed over the destination. An existing file is therefore not truncated before the new content is known to be complete, and a symlink at the destination is replaced rather than followed. A failed write is reported: cf-agent exits non-zero instead of leaving a consumer to believe in a document that was never written. The SHA-256 field is omitted, with an error logged, when the digest could not be computed -- HashFile() cannot report failure and zeroes the digest instead, and a zeroed digest presented as a real one is worse than an absent field. The document is written before GenericAgentFinalize() because computing the content digests needs the crypto (OpenSSL) library, which is deinitialized there. Changelog: Title Ticket: CFE-4716
05e18f0 to
b3a6c3d
Compare
|
The fixes are up — this supersedes the hold-off-merging notice above. Force-pushed to All seven defects from that notice are fixed, plus an eighth instance of one of them that the review missed.
The eighth defect: Two things you should look at with a critical eyeThe UTF-8 fix is in the wrong repo, deliberately. The root cause is libntech's One acceptance Tests
Discrimination was measured, not assumed: with the two source files stashed and the test file kept, Full suite Round-trip evidence for the encoding fix, via a conformant decoder rather than CFEngine's own: The |
|
Following up on the libntech point above: the root cause is filed as CFE-4730. Investigating it for that ticket turned up a second, more serious half that this PR does not touch, so flagging it here for whoever reviews the That is reachable from The two halves are exact inverses, which is why nothing caught either: libntech round-trips its own output perfectly, so any write-then-read test passes while both directions are wrong. That is also why fixing only the writer would be worse than leaving it — libntech would then misread its own files. Details and repro in CFE-4730. |
|
Following up on CFE-4730, the libntech root cause behind this PR's I want to be precise about how the two interact, because it affects how you may want to review this one. They do not conflict. The one real consequence, if libntech#293 is taken and the submodule pin later moves: I am deliberately not removing Separately, and unrelated to this PR: while fixing the codec I found that libntech's parser double-decodes string escapes — |
Added --simulate-json option to write the simulated change set as JSON
The changes computed by a --simulate run could only be rendered as
prose meant for human eyes: the manifest and diff renderers print
free-form text, and the record files in the changes chroot are an
internal, unstable format. Any program consuming the simulated change
set had to parse text that can be reworded at any time.
With --simulate-json=FILE, cf-agent also writes the change set to FILE
as a single JSON document: which files would be created, modified or
deleted (with the type, permissions, ownership, size and SHA-256
content digest they would have after the run), which files would be
renamed, and which packages would be installed or removed. The
document carries a format_version field so that consumers can detect
future changes to its structure. The option requires --simulate and an
absolute path. The prose renderers are unchanged and remain the
default; without the new option, nothing changes.
The reduction of the recorded package operations to the net set of
install and remove operations is shared with the --simulate=diff prose
renderer: DiffPkgOperations() now renders its messages from the
reduced records at printing time instead of storing pre-rendered
messages in them.
The document is written before GenericAgentFinalize() because
computing the content digests needs the crypto (OpenSSL) library,
which is deinitialized there.
Requested in #6296.