Module: RSpecTracer::RemoteCache::Archive Private
- Defined in:
- lib/rspec_tracer/remote_cache/archive.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
tar+gzip pack/extract for the remote-cache S3 payload. Pure Ruby stdlib (‘rubygems/package` + `zlib`) - no shell-out, no new gem deps, works on every supported interpreter (MRI 3.1+ and JRuby 9.4 both bundle both modules).
Wire format: single ‘.tar.gz` containing `last_run.json` at the archive root plus a `<run_id>/` directory with the 15 JSON files. Replaces the 1.x per-file layout on S3 (N+1 objects per cache -> 1 object), shrinks payload ~4-6x via gzip on the highly- redundant JSON (shared example IDs across files), and cuts the per-download/upload request count from 15+ to exactly 2 (cp for download, cp for upload).
Local cache_path layout is UNCHANGED - the archive is pack/unpack boundary for transit only. User-facing filenames in ‘USER_FACING_SURFACE.md` section 6 stay as documented; external tooling that walks `rspec_tracer_cache/` sees the same 15-file layout.
Constant Summary collapse
- CACHE_FILENAME =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
'cache.tar.gz'
Class Method Summary collapse
-
.extract(archive_path:, dest_dir:) ⇒ Object
private
Extract ‘archive_path` into `dest_dir`.
-
.pack(cache_path:, run_id:, dest_path:) ⇒ Object
private
Pack the relevant contents of ‘cache_path` into `dest_path`.
-
.safe_entry_name(name) ⇒ Object
private
Refuse absolute paths or ‘..` traversal.
Class Method Details
.extract(archive_path:, dest_dir:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Extract ‘archive_path` into `dest_dir`. Overwrites existing files (run-dir already present gets replaced). Raises on a malformed archive; caller rescues.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/rspec_tracer/remote_cache/archive.rb', line 80 def self.extract(archive_path:, dest_dir:) raise ArgumentError, 'archive_path is required' if archive_path.nil? || archive_path.empty? raise ArgumentError, 'dest_dir is required' if dest_dir.nil? || dest_dir.empty? raise ArgumentError, "missing archive at #{archive_path}" unless File.file?(archive_path) FileUtils.mkdir_p(dest_dir) File.open(archive_path, 'rb') do |file| Zlib::GzipReader.wrap(file) do |gz| Gem::Package::TarReader.new(gz) do |tar| tar.each { |entry| write_entry(entry, dest_dir) } end end end dest_dir end |
.pack(cache_path:, run_id:, dest_path:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Pack the relevant contents of ‘cache_path` into `dest_path`. Required: `cache_path/last_run.json` and `cache_path/<run_id>/`. Raises ArgumentError on a malformed cache; any I/O error during pack propagates to the caller (S3Backend wraps in S3BackendError + rescues at the orchestrator boundary for graceful degradation).
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/rspec_tracer/remote_cache/archive.rb', line 38 def self.pack(cache_path:, run_id:, dest_path:) validate_pack_args!(cache_path, run_id, dest_path) last_run, run_dir = resolve_pack_sources!(cache_path, run_id) File.open(dest_path, 'wb') do |file| Zlib::GzipWriter.wrap(file) do |gz| Gem::Package::TarWriter.new(gz) do |tar| add_file(tar, last_run, 'last_run.json') Dir[File.join(run_dir, '*.json')].each do |path| add_file(tar, path, File.join(run_id, File.basename(path))) end end end end dest_path end |
.safe_entry_name(name) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Refuse absolute paths or ‘..` traversal. Both are illegal in a well-formed cache entry name; silently dropping them beats trusting a remote-sourced value to write anywhere on disk. Public because RedisBackend reuses the same guard on hash field names (the Redis equivalent of tar entry names).
128 129 130 131 132 133 134 |
# File 'lib/rspec_tracer/remote_cache/archive.rb', line 128 def self.safe_entry_name(name) return nil if name.nil? || name.empty? return nil if name.start_with?('/') return nil if name.split('/').include?('..') name end |