Castle (castle v1.0.0)

Copy Markdown View Source

Runtime hot-code upgrade support for Elixir releases.

Forecastle prepares releases at build time; Castle manages versions on the deployed node. It unpacks, installs, commits and removes releases, resolving a target's config providers before install or commit.

Call customize/1 from the release definition in mix.exs. The remaining public functions back the bin/castle commands.

Successful commands print their result and return :ok. Castle refusals and :release_handler errors raise Castle.Error, giving bin/castle a non-zero exit status. Other exceptions, throws and exits propagate unchanged. Treat any raise over rpc as a failed command.

Commands that modify a deployment require the VM's emulator root to match the release root. This rejects include_erts: false and other layouts in which :release_handler would modify a shared Erlang installation. The diagnostic functions upgradable/0 and releases/0 remain available.

Summary

Functions

Makes vsn permanent, so that it is the version a restart boots into.

Makes a Mix release Castle-capable.

Lists the releases the system knows of, and the status of each.

Removes vsn from the system, and deletes what nothing else is using.

Confirms that vsn is the release the system is running, and has finished booting.

Unpacks a release tarball into the deployment, and reports the version.

Checks whether the running node has a valid release record for upgrades.

Functions

commit(vsn)

@spec commit(String.t()) :: :ok

Makes vsn permanent, so that it is the version a restart boots into.

bin/castle commit [<vsn>] calls this function. Without a version, the command selects the current release and exits non-zero when none is awaiting commit.

Castle resolves the target's config providers again before promotion, storing the configuration a boot at commit time would produce. An explicit commit of the permanent version still performs this step.

Raises Castle.Error if the configuration could not be expanded, or if the version cannot be promoted. This includes staged, rolled-back, superseded and unknown versions.

customize(opts)

@spec customize(keyword()) :: keyword()

Makes a Mix release Castle-capable.

Adds Forecastle's build steps and returns the updated release options.

# mix.exs
defp releases do
  [
    my_app: fn ->
      [
        include_executables_for: [:unix],
        steps: [:assemble, :tar]
      ]
      |> Castle.customize()
    end
  ]
end

Define the release with fn -> ... end so Mix evaluates it after Castle has been compiled.

Steps

Existing steps retain their order. If :steps is absent, this function uses [:assemble, :tar]. It preserves an explicit list and warns when the list has no :tar.

Forecastle generates the relup after custom steps that change the release and immediately before :tar. If a custom step packages the release without :tar, place &Forecastle.generate_relup/1 immediately before that step. Split steps that both modify and package the release. A step after :tar must not modify or repackage it.

Upgrades

upgrade_from: names the supported source releases:

my_app: fn ->
  [
    include_executables_for: [:unix],
    upgrade_from: ["tar:artifacts/my_app-1.0.0.tar.gz"]
  ]
  |> Castle.customize()
end

A baseline may be a shipped tar: archive, an assembled rel: release or a ref: git ref. A path without a prefix means rel:. Forecastle generates both directions for every baseline. Prefer tar: when the shipped artifact is available because a rebuilt baseline may differ from the deployed release.

Forecastle validates upgrade_from:. It rejects malformed or duplicate values and a project-root relup supplied alongside the option. Omitting the option skips relup generation.

The project must also provide:

  • An appup for each owned application upgraded in place, configured with the :appup project key and compilers: Mix.compilers() ++ [:appup]. See mix help castle.relup for the rules.
  • A relup generated from upgrade_from:, or one generated by mix castle.relup and left in the project root.
  • include_executables_for: [:unix].

Forecastle appends Castle's setup to the generated env.sh or to a custom rel/env.sh.eex.

install(vsn, rel_dir \\ rel_dir(), handler \\ :release_handler, peer \\ Peer, deployment \\ Deployment)

@spec install(String.t(), Path.t(), module(), module(), module()) :: :ok

Installs vsn and makes it the version the system is running.

bin/castle install <vsn> calls this function after unpack/1 stages the version.

Castle checks the deployment and release record, resolves the target's config providers in a temporary VM, and prepares any emulator-restart marker before calling :release_handler.install_release/1.

A hot upgrade reports the new and previous running versions. An upgrade that restarts the emulator reports that the version was installed and remains provisional. bin/castle install then polls running/1 until the target has finished booting. Automation that calls install/1 over rpc must perform the same check.

The installed version remains provisional until commit/1. An ordinary restart before commit boots the previous permanent version. A restart_emulator transition boots the target for its installation restart, but later restarts still use the permanent version until commit.

Castle serialises installs on the local Erlang node. A pending restart install owns its launcher marker and blocks another restart install from replacing it.

The four extra arguments

install/1 is the supported form. The defaulted arguments on install/2 through install/5 are test seams, not deployment options.

releases()

@spec releases() :: :ok

Lists the releases the system knows of, and the status of each.

bin/castle releases calls this function. It prints one line per release with its :release_handler status: permanent, current, unpacked or old.

unpacked includes staged releases and releases returned to that state after a failed or rolled-back install. A node with no known releases prints nothing. This read-only command remains available when mutating commands are refused.

remove(vsn)

@spec remove(String.t()) :: :ok

Removes vsn from the system, and deletes what nothing else is using.

bin/castle remove <vsn> calls this function. It removes the version directory, unreferenced application directories, and an emulator directory unused by any remaining release.

Raises Castle.Error for the permanent version or an unknown version.

running(vsn)

@spec running(String.t()) :: :ok

Confirms that vsn is the release the system is running, and has finished booting.

Success prints nothing. A different running version or an incomplete boot raises Castle.Error with the current state.

bin/castle install polls this function. It accepts the current release, or the permanent release when no release is current, after the boot script reaches its started marker.

unpack(name)

@spec unpack(String.t()) :: :ok

Unpacks a release tarball into the deployment, and reports the version.

bin/castle unpack <vsn> calls this function. Place <release-name>-<vsn>.tar.gz in the deployment's releases directory first.

The command extracts the applications and records the version as unpacked. It does not change the running version.

Raises Castle.Error if the node cannot be upgraded from. See upgradable/0. RELDIR and the SASL releases_dir option are not supported; see issue #23.

upgradable()

@spec upgradable() :: :ok

Checks whether the running node has a valid release record for upgrades.

bin/castle upgradable calls this function. Success prints nothing. A node using a record synthesised by :release_handler raises Castle.Error with recovery instructions.

unpack/1 and install/1 perform the same check when they act. A prior call to upgradable/0 is diagnostic only.

The release-record file is normally <root>/releases/RELEASES. RELDIR or the SASL releases_dir option can move the file read by :release_handler.