Module: SpecGuard::RSpec::ValidatorBackend::Installer
- Defined in:
- lib/specguard/rspec/validator_backend.rb
Overview
Obtains a validate-intent binary for the DEFAULT-ON path: the
platform-matched prebuilt asset from open-test-intent's GitHub release,
verified against the release's SHA256SUMS manifest, cached under the
user's cache dir so the network is touched once per machine.
Why the download is verified the way Runner#verify! verifies
The posture is inherited from the schema-contract check: a downloaded
blob is not a binary because we asked for one. The release publishes a
SHA256SUMS manifest beside its assets, and the gem checks the ONE row
that names this platform's asset — the same choice
open-test-intent's own scripts/install.sh makes, for the reason its
header gives (sha256sum -c would report the three rows this host did
not stage as missing files). Only a row that matches BOTH the digest
and the asset name counts, so a manifest that does not describe the
fetched bytes is a refusal and not a pass.
Why a failed fetch is an exit 2 naming two remediations
There is no Ruby fallback any more, so "no binary" must be loud: the
message names the env var (point at a binary you already have) and the
install.sh curl|sh path (the CI-pinned alternative). A first run with
no network is the expected shape of this failure and the message is
written for it.
Constant Summary collapse
- RELEASE_TAG =
The release the gem resolves against. Pinned by tag, not "latest": which validator validated a CI job must not change because somebody published a new release, and Runner#verify_schema_contract! pins the fetched binary to this gem's vendored schema anyway. v0.1.4 is the first release whose JSON findings carry
intenton passing rows; v0.1.3 validated the same schema but reported no payload, which the formatter's mapping reads as "unannotated" — every annotation silently dropped while the verdicts stayed green (found via yatfa-ai/specguard's CI reporting 0.0% annotated after its lock jumped 0.2.3 -> 0.3.1 and the SPGD-867 binary cutover ran for real). "v0.1.4"- REPOSITORY =
"yatfa-ai/open-test-intent"- DOWNLOAD_BASE =
"https://github.com/#{REPOSITORY}/releases/download/#{RELEASE_TAG}"- INSTALL_SH =
The CI-pinned alternative to the auto-install: install the same verified release artifact onto PATH and point the env var at it.
"curl -fsSL " \ "https://raw.githubusercontent.com/#{REPOSITORY}/#{RELEASE_TAG}/scripts/install.sh | sh"
- REMEDIATIONS =
Both remediations, spelled once and reused by every refusal, so the message cannot drift between the ways the fetch can fail.
"set #{ValidatorBackend::ENV_VAR} to a validate-intent binary, " \ "or install one with: #{INSTALL_SH}"
- CACHE_DIR_VAR =
Where the cached binary lives when the user has not redirected it. Honours
SPECGUARD_CACHE_DIR(hermetic CI caches), then XDG, then~/.cachefor hosts without XDG set. "SPECGUARD_CACHE_DIR"- OS_FOR =
RUBY_PLATFORM-> the release's os/arch vocabulary. Anything outside the four published assets is unsupported: guessable names (freebsd?) would produce a 404 masquerading as a policy, so the mapping is closed and the refusal names the platform it was asked about. { /linux/ => "linux", /darwin|mac/ => "darwin" }.freeze
- ARCH_FOR =
{ /x86_64|amd64|x64/ => "amd64", /aarch64|arm64/ => "arm64" }.freeze
- OPEN_TIMEOUT =
Net::HTTPtimeouts. Short on purpose: this runs at the front of a lint step, and a first-run fetch that hangs is worse than one that fails fast with the remediation message. 10- READ_TIMEOUT =
30- MAX_REDIRECTS =
5
Class Method Summary collapse
-
.asset_name(platform: RUBY_PLATFORM) ⇒ Object
The release asset name for this host.
- .cache_dir(env) ⇒ String
-
.install(asset, destination) ⇒ Object
Downloads the asset plus its manifest, verifies, and installs atomically (temp file + rename, chmod 0755) so a killed download can never leave a half-written binary the next run mistakes for good — or worse, executes.
-
.obtain(env: ENV) ⇒ String
Path to an executable, SHA256SUMS-verified binary.
Class Method Details
.asset_name(platform: RUBY_PLATFORM) ⇒ Object
The release asset name for this host.
440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/specguard/rspec/validator_backend.rb', line 440 def asset_name(platform: RUBY_PLATFORM) os = OS_FOR.find { |pattern, _| pattern.match?(platform) }&.last arch = ARCH_FOR.find { |pattern, _| pattern.match?(platform) }&.last if os.nil? || arch.nil? raise ValidatorError, "no prebuilt validate-intent release asset for #{platform} " \ "(#{RELEASE_TAG} publishes #{OS_FOR.values.uniq.product(ARCH_FOR.values.uniq) \ .map { |o, a| "#{o}/#{a}" }.join(', ')}) — #{REMEDIATIONS}" end "validate-intent-#{os}-#{arch}" end |
.cache_dir(env) ⇒ String
456 457 458 459 460 461 462 463 464 |
# File 'lib/specguard/rspec/validator_backend.rb', line 456 def cache_dir(env) override = env[CACHE_DIR_VAR].to_s.strip xdg = env["XDG_CACHE_HOME"].to_s.strip root = override.empty? ? (xdg.empty? ? File.join(Dir.home, ".cache") : xdg) : override # Named for the gem, so a cached validator binary survives gem upgrades # within one name and is re-downloaded once — and only once — when the # gem itself changes name. File.join(root, "specguard-ruby", "validate-intent", RELEASE_TAG) end |
.install(asset, destination) ⇒ Object
Downloads the asset plus its manifest, verifies, and installs atomically (temp file + rename, chmod 0755) so a killed download can never leave a half-written binary the next run mistakes for good — or worse, executes.
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'lib/specguard/rspec/validator_backend.rb', line 470 def install(asset, destination) manifest = download("#{DOWNLOAD_BASE}/SHA256SUMS") expected = manifest_digest(manifest, asset) bytes = download("#{DOWNLOAD_BASE}/#{asset}") actual = Digest::SHA256.hexdigest(bytes) unless actual == expected raise ValidatorError, "downloaded #{DOWNLOAD_BASE}/#{asset} has sha256:#{actual}, but the release " \ "manifest says sha256:#{expected} — the download is not the artifact the " \ "release published, so it was not installed; #{REMEDIATIONS}" end dir = File.dirname(destination) FileUtils.mkdir_p(dir) tmp = "#{destination}.tmp.#{Process.pid}" File.binwrite(tmp, bytes) File.chmod(0o755, tmp) File.rename(tmp, destination) destination rescue ValidatorError raise rescue StandardError => e # Net::HTTP's failures (SocketError, OpenTimeout, EOFError, ...), # and any filesystem failure writing the cache. All of them mean # "no binary could be obtained", which is the one refusal this # module has — exit 2, both remediations. raise ValidatorError, "could not obtain validate-intent from #{DOWNLOAD_BASE}: " \ "#{e.class}: #{e.}; #{REMEDIATIONS}" end |
.obtain(env: ENV) ⇒ String
Returns path to an executable, SHA256SUMS-verified binary.
426 427 428 429 430 431 432 433 |
# File 'lib/specguard/rspec/validator_backend.rb', line 426 def obtain(env: ENV) asset = asset_name destination = File.join(cache_dir(env), asset) return destination if cached?(destination) install(asset, destination) end |