lib/distillery/plugins/link_config.ex

defmodule Releases.Plugin.LinkConfig do
  @moduledoc """
    Distillery plugin to link the `vm.args` or `sys.config` file on deploy hosts.

    Because distillery uses `:systools_make.make_tar(...)` to create the release
    tar which resolves all links using the `:dereference` option, the release
    tar needs to be repackaged including the links. To be able use this plugin,
    it must be added in the `rel/config.exs` distillery config as plugin like this:

    ```
    environment :prod do
      ..
      plugin Releases.Plugin.LinkConfig
    end
    ```
  """
  use Distillery.Releases.Plugin


  def before_assembly(_, _), do: nil

  def after_assembly(_, _), do: nil

  def before_package(_, _), do: nil

  def after_package(%Release{version: version, profile: profile, name: name}, _) do
    # repackage release tar including link, because tar is generated using `:systools_make.make_tar(...)`
    # which resolves the links using the `:dereference` option when creating the tar using the
    # `:erl_tar` module.
    output_dir = profile.output_dir
    tmp_dir = "_edeliver_release_patch"
    tmp_path = Path.join [output_dir, "releases", version, tmp_dir]
    files_to_link = [
      {System.get_env("LINK_VM_ARGS"),    Path.join([tmp_path, "releases", version, "vm.args"])},
      {System.get_env("LINK_SYS_CONFIG"), Path.join([tmp_path, "releases", version, "sys.config"])},
    ] |> Enum.filter(fn {source, _dest} ->
      case source do
        <<_,_::binary>> -> true
        _ -> false
      end
    end)
    if Enum.count(files_to_link) > 0 do
      info "Repackaging release with links to config files"
      try do
        tar_file = Path.join [output_dir, "releases", version, "#{name}.tar.gz"]
        true = File.exists? tar_file
        :ok = File.mkdir_p tmp_path
        ln_binary = <<_,_::binary>>  = System.find_executable "ln"
        debug "Extracting release tar to #{tmp_dir}"
        :ok = :erl_tar.extract(tar_file, [{:cwd, to_charlist(tmp_path)}, :compressed])
        directories_to_include = for dir <- File.ls!(tmp_path), do: {to_charlist(dir), to_charlist(Path.join(tmp_path, dir))}
        for {source, destination} <- files_to_link do
          debug "Linking #{source} to #{destination}"
          {_, 0} = System.cmd ln_binary,  ["-sf", source, destination], stderr_to_stdout: true
        end
        debug "Recreating release tar including links"
        :ok = :erl_tar.create(tar_file, directories_to_include, [:compressed])
      after
        tmp_path_exists? = File.exists?(tmp_path) && File.dir?(tmp_path)
        tmp_path_empty? = tmp_path_exists? && File.ls!(tmp_path) == []
        tmp_path_contains_rel? = File.exists?(Path.join(tmp_path, "lib")) || File.exists?(Path.join(tmp_path, "releases"))
        if tmp_path_exists? && (tmp_path_empty? || tmp_path_contains_rel?) do
          debug "Removing tmp dir used for repackaging tar: #{tmp_path}"
          File.rm_rf!(tmp_path)
        end
      end
    end
    nil
  end
  def after_package(_, _), do: nil

  def after_cleanup(_, _), do: nil

end