feat(snapshot s3): fingerprint S3 buckets from a virtual tree - #1180
Conversation
|
Claude finished @mbevc1's task in 2m 54s —— View job Follow-up review completeReviewed the diff introduced by the latest commits (
Finding status:
New issues in the changed hunks: none found. The rework (repeated-key check ordering, All 5 review threads are already marked resolved on GitHub, consistent with the above. |
Record the decision to stop laying bucket objects out by key on the operator's filesystem. Objects download to anonymous temp files, are hashed and deleted, and digest.VirtualDirSha256 computes the fingerprint from (key, sha256) pairs. Captures the compatibility contract, the shared key rule for content and metadata mode, the rejected CRC64 alternative, and the delivery slices.
path.Clean folds a/../b onto b silently, so the rejection has to run on the raw key. Also spell out that the rule exists for fingerprint compatibility, not safety: DirSha256 can never produce a tree holding ".", ".." or an empty name.
Name the three properties the virtual-tree change must hold, snapshot equals attestation, snapshot equals its own history, and no object is dropped silently, together with the test that holds each one.
…a filesystem VirtualDirSha256 reproduces DirSha256 from (path, sha256) pairs alone: it builds the tree, walks it in filepath.WalkDir order and hashes each entry's base name plus each file's content digest. SingleVirtualFile mirrors containsSingleFile so a one-file tree can take the FileSha256 branch. Every equivalence test materialises the tree on disk and requires DirSha256 to agree, so the two cannot drift silently. No callers yet. This is the first slice of moving kosli snapshot s3 off key-named local files (see docs/adr/20260911-s3-fingerprint-from-virtual-tree.md).
…colliding key virtualPathForS3Key turns an object key into the path it occupies in the virtual tree that will be fingerprinted. A ".." segment is rejected on the raw key, before path.Clean can fold it onto a sibling; a leading slash, "." segments and doubled slashes fold exactly as filepath.Join did, so existing fingerprints are unchanged. Nothing is created under the path, so reserved names, colons, backslashes and overlong components are ordinary names and there is no per-OS branch. virtualPathsForS3Keys validates a whole key set at once and reports every problem together: rejected keys, keys folding onto one path, and an object whose path is also a directory holding other objects, capped at ten. No callers yet; content mode switches to it in a later slice.
VirtualDirSha256 takes the rules of the tree's root .kosli_ignore and excludes what DirSha256 would exclude on disk. Rather than reimplement what the globs appear to mean, virtualFS reproduces filepathx.Glob, filepath.Glob and filepath.Walk step for step over the virtual tree, so their quirks come out identical: a literal "**/x" never matches at the root because the pieces concatenate to a double slash, "**/*.log" does, and excluding "logs/*" leaves an empty directory whose name is still hashed. Exclusion therefore happens inside the tree walk, not by filtering the file list. The root ignore file is never excluded by its own rules, as on disk. ParseIgnoreRules is extracted from excludePathsFromFile so callers that hold the file's bytes get the same reading DirSha256 gives the file. Every rule set in the equivalence test is materialised on disk and fingerprinted with DirSha256, and the virtual digest must match; rows that should change the digest also assert that they do.
…s local paths Each object now downloads to an anonymous temp file that is hashed and removed; the fingerprint comes from digest.VirtualDirSha256 over the (key, sha256) pairs, which reproduces what DirSha256 gave the same tree on disk. No key ever names a file, so traversal, overwrites, reserved names, case folding and component length limits stop being properties of this code, and the fingerprint is the same on every operating system. A root .kosli_ignore is downloaded first and its rules applied; objects the rules exclude are not downloaded at all. digest.FilesNeedingContent decides which files need a digest, and VirtualDirSha256 refuses a tree that needs a digest it was not given, so a skipped download can never leak into a fingerprint. Keys that cannot form a directory tree (a ".." segment, two keys folding onto one path, an object that is also a prefix) fail the snapshot and name every key involved. Fingerprints recorded against main are pinned before the switch and unchanged after it; a bucket built from a directory fingerprints as DirSha256 fingerprints the directory. localPathForS3Key, the O_EXCL and ENOTDIR handling and containsSingleFile are gone with their tests. The cmd/kosli TestSnapshotS3 suite needs the local Kosli server and is left to CI.
Drop references that only made sense during development: the throwaway experiment and the gitignored TODO.md. Name the tests that hold each guarantee, describe the .kosli_ignore resolution as the filepathx simulation that was actually built, mark parallel downloads as delivered separately in #1167, and correct the claim that the codebase gets smaller: the platform-dependent code is gone, but the faithful glob simulation costs more lines than the fenced layout it replaces.
… rules and malformed listings
Folds the fixes found in review of the virtual-tree fingerprint into one
change, so the ignore-rule mirror and the S3 listing hold up against
input the bucket's writers control.
Ignore-rule mirror (internal/digest):
- validate a rule up front as filepath.Glob does, on the first "**" piece
only, so a malformed rule fails on both sides and "nonexistent/**/a["
stays a no-op on both
- bound glob recursion and virtual path depth at filepath's separator
limit
- skip a rule that leaves the tree even when it names the root
("../tree/x"), which the cleaned pattern alone let back in
- do not protect a directory named .kosli_ignore from the rules, as
ignoreFilePathInTree does not on disk
- keep one IgnoreFileName constant; drop references to deleted functions
S3 listing and fingerprint (internal/aws):
- tolerate a listing entry without LastModified, and error rather than
panic or send a zero timestamp when no matching object carries one
- reject a listing entry with no key or a key listed twice, before the
filters, rather than drop an object silently
- bound the keys one collision line names, as the problem list already
was, and name the count
- name the object key when hashing a downloaded object fails
- decide the single-object case with SingleVirtualFile on the manifest;
name the bucket's ignore file only for a bad rule (ignoreRuleError)
Each fix carries a test that was red before it: the DirSha256
equivalence table gains the re-entering, wildcard-dipping and
directory-named-ignore-file rows, and the listing faults are pinned
against the fake S3 client.
Backslash-in-rule semantics on Windows, the malformed-listing rejections, the path depth bound, and the shared cap constant.
…ach the fingerprint Checking before the filters kept one string per listed key, so a large bucket narrowed by --include-regex still held the whole listing in memory. Checked after the filters, memory is bounded by the filtered set, which is what the rest of the pipeline already holds.
The glob does find a root x, spelled with a double slash the walk's cleaned paths never equal; a root directory of that name keeps its name and loses its contents. The comment and the ADR said it never matched.
Fingerprinting a tree means holding it, so peak memory rises from one listing page to every object that survives the filters.
84e69dd to
7ab9a92
Compare
kosli-cli 2.42.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre># New features - `kosli snapshot s3`: added `--download-concurrency` and `--download-budget` flags to control parallel downloads and peak temporary disk usage when fingerprinting S3 buckets. - `kosli attest sonar`: `--sonar-revision` can now be used together with `--pull-request`; when provided, the command fails if the pull request's latest analysis is not of the specified revision. # Improvements - `kosli snapshot s3`: S3 object keys are no longer used as local file names; each object is downloaded to a temporary file, hashed, and removed, so any valid S3 key can be fingerprinted on any OS. - `kosli get flow` and `kosli list flows`: the `Visibility` field is no longer shown in table output (it is a legacy field with no effect on access). - `kosli list flows`: fixed a panic when a flow has no tags. <!-- Release notes generated using configuration in .github/release.yml at v2.42.0 --> ## What's Changed * ci(kosli): attest SBOMs with the sbom type by @AlexKantor87 in kosli-dev/cli#1186 * feat(snapshot s3): fingerprint S3 buckets from a virtual tree by @mbevc1 in kosli-dev/cli#1180 * fix(ci): wait for staging to settle before picking a server image by @AlexKantor87 in kosli-dev/cli#1190 * feat(snapshot s3): download objects in parallel within a count and byte budget by @mbevc1 in kosli-dev/cli#1191 * feat: allow --sonar-revision with --pull-request to validate analysis commit by @mbevc1 in kosli-dev/cli#1193 * chore(list-flows): drop the VISIBILITY column from the table output by @dangrondahl in kosli-dev/cli#1187 * chore(get-flow): drop the Visibility row from the table output by @dangrondahl in kosli-dev/cli#1188 * fix(snapshot k8s): do not abort the snapshot when a Running pod has an empty imageID by @dangrondahl in kosli-dev/cli#1195 * test(fingerprint): cover OCI capture cleanliness against a fake registry by @mbevc1 in kosli-dev/cli#1197 **Full Changelog**: kosli-dev/cli@v2.41.0...v2.42.0 </pre> <p>View the full release notes at <a href="https://github.com/kosli-dev/cli/releases/tag/v2.42.0">https://github.com/kosli-dev/cli/releases/tag/v2.42.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!20398
Change so
kosli snapshot s3no longer lays bucket objects out under their keys on the operator's filesystem. Each object downloads to an anonymous temp file, is hashed and removed, anddigest.VirtualDirSha256computes the fingerprint from the (key, sha256) pairs, reproducing whatdigest.DirSha256gives the same tree on disk. Every existing fingerprint is preserved (TestPinnedFingerprints,TestMatchesAttestedDirectory), a root.kosli_ignoreis honoured exactly as on disk (TestMatchesDirSha256materialises the tree and compares), and no object is ever dropped silently.Object keys are checked by one platform-independent rule: a
..segment, two keys folding onto one path, an object that is also a prefix, and malformed listings are errors that name every key involved. Keys that only an operating system would reject, such asCON, colons, backslashes or over-long components, snapshot again. The #1155 filesystem safeguards are deleted with the layout they protected.The ADR in
docs/adr/records the decision, the compatibility contract, the rejected CRC64 alternative and the consequences. Parallel downloads with a byte budget follow in #1167, which stacks on this branch.Replaces #1178, whose review rounds are folded into the
fix(snapshot s3): harden…commit here. Closes #1155's follow-up; related #1069.Checklist
charts/k8s-reporter/) updated, if needed. Note: these changes live in a separate PR