Module: HotCell

Defined in:
lib/hot_cell/cell.rb,
lib/hot_cell/cells.rb,
lib/hot_cell/client.rb,
lib/hot_cell/install.rb,
lib/hot_cell/railtie.rb,
lib/hot_cell/failures.rb,
lib/hot_cell/transport.rb,
lib/hot_cell/client/version.rb

Defined Under Namespace

Modules: Install, Transport Classes: Cell, Client, PermanentFailure, Railtie, TransientFailure

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.groupObject

The group both sides hold, so a cell can open a caller's file by name. An operation that hands a tool a filename re-opens the descriptor as /dev/fd/N, and the kernel rechecks that open against the cell's own credentials rather than the caller's — so a file only this application can read fails with EACCES however the descriptor was passed. Set this to the cell's gid and put the application in that group; the client then narrows each descriptor's mode on the way out. See Client#wrap.

Leave it unset where both sides already run as one user, which is how development runs.



18
19
20
# File 'lib/hot_cell/cells.rb', line 18

def group
  @group
end

.loggerObject



31
32
33
# File 'lib/hot_cell/cells.rb', line 31

def logger
  @logger ||= Logger.new($stderr)
end

.rootObject

The directory a cell's name resolves under, holding one subdirectory per cell. Unset means no cell is reachable at all, which is the off position of the whole rollout.



9
10
11
# File 'lib/hot_cell/cells.rb', line 9

def root
  @root
end

Class Method Details

.cell(name) ⇒ Object



52
53
54
55
56
# File 'lib/hot_cell/cells.rb', line 52

def cell(name)
  cells.fetch(name.to_s) do
    raise UnregisteredCell, "no cell named #{name.to_s.inspect} is registered (#{cells.keys.inspect})"
  end
end

.cell?(name) ⇒ Boolean

Whether a name is registered, for a caller with a fallback rather than a requirement.

Returns:

  • (Boolean)


59
60
61
# File 'lib/hot_cell/cells.rb', line 59

def cell?(name)
  cells.key?(name.to_s)
end

.cellsObject



48
49
50
# File 'lib/hot_cell/cells.rb', line 48

def cells
  @cells ||= {}
end

.clientsObject

Every HotCell::Client subclass, so a boot check can tell which cell is expected to carry what. This records what the process has loaded rather than what it has configured, so resetting registrations leaves it alone: the classes are still defined either way.



66
67
68
# File 'lib/hot_cell/cells.rb', line 66

def clients
  @clients ||= []
end

.describe_cellsObject

Call once at boot, after registering. Warns and carries on; see Cell#describe.



71
72
73
74
# File 'lib/hot_cell/cells.rb', line 71

def describe_cells
  warn_about_group
  cells.each_value.to_h { |cell| [ cell.name, cell.describe ] }
end

.register(name, **options) ⇒ Object

Cells are registered once.

HotCell.root = ENV.fetch("HOTCELL_ROOT", "/run/hotcell")

HotCell.register "active_storage",
permanent: ActiveStorage::PreviewError,
transient: MyApp::ConversionTemporarilyUnavailable

HotCell.register "archiver", dir: -> { ENV["HOTCELL_ARCHIVER_DIR"] }, timeout: 300


44
45
46
# File 'lib/hot_cell/cells.rb', line 44

def register(name, **options)
  Cell.new(name, **options).tap { |cell| cells[cell.name] = cell }
end

.reset_registrations!Object

Test support. Named apart from the server gem's own reset, because both gems open this module and a shared name would mean whichever loaded last silently won.



91
92
93
94
95
# File 'lib/hot_cell/cells.rb', line 91

def reset_registrations!
  @cells = nil
  @root = nil
  @group = nil
end

.warn_about_groupObject

A group this process does not hold cannot be given to a file, so without this the first conversion fails as EPERM from the client's own chown — after the deployment is live and carrying traffic. The check is local and needs no cell, so it reports a missing group-add even when every cell is down.



79
80
81
82
83
84
85
86
87
# File 'lib/hot_cell/cells.rb', line 79

def warn_about_group
  return if group.nil? || group == Process.gid || group == Process.egid
  return if Process.groups.include?(group)

  logger.warn "hotcell: HotCell.group is #{group} and this process is in #{Process.groups.sort.inspect}, " \
              "so it cannot put a descriptor in that group and every conversion will fail with EPERM. " \
              "Add the group to this container (Kamal: `group-add` under the role's `options:`), or " \
              "unset HotCell.group where both sides run as one user."
end