Class: ReactOnRailsPro::RollingDeployAdapters::Http

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails_pro/rolling_deploy_adapters/http.rb

Overview

Built-in HTTP rolling-deploy adapter. Pairs with ReactOnRailsPro::RollingDeploy::BundlesController on the running Rails server: the controller exposes the current deployment’s bundles and the adapter (running in the next deployment’s build CI) fetches them.

The promise is “zero-infra default”: no S3 bucket, no IAM, no extra gem. The currently-deployed Rails server already has the bundles + companion assets sitting on disk; this adapter pulls them via authenticated HTTP.

Configuration (see docs/pro/rolling-deploy-http-adapter.md):

ReactOnRailsPro.configure do |config|
  config.rolling_deploy_adapter      = ReactOnRailsPro::RollingDeployAdapters::Http
  config.rolling_deploy_token        = ENV.fetch("ROLLING_DEPLOY_TOKEN")
  config.rolling_deploy_previous_url = ENV["ROLLING_DEPLOY_PREVIOUS_URL"]
end

Error contract matches the rolling_deploy_adapter protocol: every exception is caught and reported as a warning so a failed seed degrades to the runtime 410-retry fallback rather than failing the build.

Constant Summary collapse

DEFAULT_OPEN_TIMEOUT_SECONDS =

Per-request HTTP timeouts. The outer Timeout.timeout in RollingDeployCacheStager bounds the total wall-clock budget (10s for discovery, 30s for fetch); these inner timeouts let a hung server fail before the outer wrapper interrupts mid-write, which is more reliable than relying on the thread-level Timeout.timeout that may interrupt at a random execution point.

5
DEFAULT_READ_TIMEOUT_SECONDS =
25
MANIFEST_READ_TIMEOUT_SECONDS =

Manifest discovery is wrapped in a 10s outer budget by RollingDeployCacheStager.

4
DEFAULT_MAX_SIZE =

Maximum uncompressed payload accepted from /bundles/:hash. Mirrors the tarball helper default so a misbehaving or malicious server cannot exhaust disk via a zip-bomb-style response.

ReactOnRailsPro::RollingDeploy::Tarball::DEFAULT_MAX_SIZE
LOG_PREFIX =
"[ReactOnRailsPro::RollingDeployAdapters::Http]"
BUNDLE_ENTRY_NAME =

Wire-format constant: must stay in sync with ‘ReactOnRailsPro::RollingDeploy::BundlesController::BUNDLE_ENTRY_NAME`. The controller serves the bundle file under this entry name; if the two ever diverge the client will fail to locate the bundle after extracting the tarball.

"bundle.js"

Class Method Summary collapse

Class Method Details

.fetch(bundle_hash) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 92

def fetch(bundle_hash)
  base = configured_previous_url
  return nil if base.nil?
  return nil if hash_invalid?(bundle_hash)

  if token_missing?
    return warn_and_return("rolling_deploy_token is not configured; skipping fetch(#{bundle_hash.inspect})",
                           nil)
  end

  dir = bundle_dir(bundle_hash)
  FileUtils.mkdir_p(dir)
  tarball_body = download_bundle_tarball(base, bundle_hash)
  return cleanup_and_return(dir, nil) if tarball_body.nil?

  extract_payload(tarball_body, dir, bundle_hash)
rescue StandardError => e
  cleanup_and_return(dir, nil) if dir
  warn_and_return("fetch(#{bundle_hash.inspect}) failed: #{e.class}: #{e.message}", nil)
end

.previous_bundle_hashesObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 62

def previous_bundle_hashes
  base = configured_previous_url
  return [] if base.nil?

  if token_missing?
    return warn_and_return("rolling_deploy_token is not configured; skipping manifest fetch",
                           [])
  end

  response = http_get(
    URI("#{base}/manifest"),
    read_timeout: MANIFEST_READ_TIMEOUT_SECONDS
  )
  return warn_and_return("manifest returned HTTP #{response.code}", []) unless response.is_a?(Net::HTTPSuccess)

  parsed = JSON.parse(response.body)
  # Filter manifest hashes through SAFE_HASH_PATTERN before returning
  # so server-supplied strings never appear verbatim in downstream
  # warning logs. Each hash is re-validated inside `fetch`, so this is
  # defense-in-depth — nothing unsafe could reach the filesystem layer
  # — but it keeps log lines from a misbehaving or compromised server
  # from echoing arbitrary content.
  Array(parsed["hashes"])
    .map(&:to_s)
    .reject(&:empty?)
    .grep(ReactOnRailsPro::RollingDeploy::SAFE_HASH_PATTERN)
rescue StandardError => e
  warn_and_return("previous_bundle_hashes failed: #{e.class}: #{e.message}", [])
end

.upload(_bundle_hash, bundle:, assets:) ⇒ Object

Intentional no-op. The running Rails server IS the artifact store —bundle + companion assets are already on local disk where the mountable BundlesController will serve them on the next deploy’s build CI. Documented in docs/pro/rolling-deploy-http-adapter.md.



117
118
119
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 117

def upload(_bundle_hash, bundle:, assets:)
  # See class doc above.
end