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

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.

Raises:

  • (ArgumentError)


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