Current section

Files

Jump to
dagger lib dagger core engine downloader.ex
Raw

lib/dagger/core/engine/downloader.ex

defmodule Dagger.Core.Engine.Downloader do
@moduledoc false
@dagger_bin_prefix "dagger-"
@dagger_default_cli_host "dl.dagger.io"
@doc false
def download(cli_version, opts \\ []) do
cli_host = opts[:cli_host] || @dagger_default_cli_host
cache_dir = :filename.basedir(:user_cache, "dagger")
{os, _} = target()
bin_name = dagger_bin_name(cli_version, os)
cache_bin_path = Path.join([cache_dir, bin_name])
perm = 0o700
with :ok <- File.mkdir_p(cache_dir),
:ok <- File.chmod(cache_dir, perm),
{:error, :enoent} <- File.stat(cache_bin_path),
temp_bin_path = Path.join([cache_dir, "temp-" <> bin_name]),
:ok <- extract_cli(cli_host, cli_version, temp_bin_path),
:ok <- File.chmod(temp_bin_path, perm),
:ok <- File.rename(temp_bin_path, cache_bin_path) do
{:ok, cache_bin_path}
else
{:ok, _stat} -> {:ok, cache_bin_path}
error -> error
end
end
defp extract_cli(cli_host, cli_version, bin_path) do
req = Req.new(url: cli_archive_url(cli_host, cli_version))
with {:ok, response} <- Req.get(req, raw: true),
:ok <- verify_checksum(response, cli_host, cli_version) do
{_, %{body: files}} = Req.Steps.decode_body({req, response})
{_, dagger_bin} =
Enum.find(files, fn {filename, _bin} ->
{os, _} = target()
filename == dagger_bin_in_archive(os)
end)
File.write(bin_path, dagger_bin)
end
end
defp verify_checksum(response, cli_host, cli_version) do
calculated_checksum = :crypto.hash(:sha256, response.body) |> Base.encode16(case: :lower)
case expected_checksum(cli_host, cli_version) do
{:ok, expected_checksum} ->
if :crypto.hash_equals(calculated_checksum, expected_checksum) do
:ok
else
{:error, "checksum mismatch: expected #{expected_checksum}, got #{calculated_checksum}"}
end
error ->
error
end
end
defp expected_checksum(cli_host, cli_version) do
archive_name = default_cli_archive_name(target(), cli_version)
{:ok, checksum_map} = checksum_map(cli_host, cli_version)
expected_value =
Enum.find_value(checksum_map, fn {key, value} ->
if to_string(key) == archive_name, do: value
end)
if is_nil(expected_value) do
{:error, "expected value find error"}
else
{:ok, expected_value}
end
end
defp checksum_map(cli_host, cli_version) do
try do
with {:ok, response} <- Req.get(checksum_url(cli_host, cli_version)) do
checksum_map =
response.body
|> String.split("\n", trim: true)
|> Enum.map(&String.split/1)
|> Enum.map(fn
[hash, file] ->
{file, hash}
list ->
raise "Invalid checksum line: #{length(list)}"
end)
|> Enum.into(%{})
{:ok, checksum_map}
end
rescue
reason in RuntimeError -> {:error, reason}
end
end
defp dagger_bin_in_archive(:windows), do: ~c"dagger.exe"
defp dagger_bin_in_archive(_), do: ~c"dagger"
defp checksum_url(cli_host, cli_version) do
"https://#{cli_host}/dagger/releases/#{cli_version}/checksums.txt"
end
defp cli_archive_url(cli_host, cli_version) do
archive_name = default_cli_archive_name(target(), cli_version)
"https://#{cli_host}/dagger/releases/#{cli_version}/#{archive_name}"
end
defp target() do
case :os.type() do
{:win32, _} ->
{:windows, "amd64"}
{:unix, osname} ->
[arch | _] = :erlang.system_info(:system_architecture) |> to_string() |> String.split("-")
arch =
case arch do
"aarch64" -> "arm64"
"x86_64" -> "amd64"
end
{osname, arch}
end
end
defp default_cli_archive_name({os, arch}, cli_version) do
ext =
case os do
os when os in [:linux, :darwin] -> "tar.gz"
:windows -> "zip"
end
"dagger_v#{cli_version}_#{os}_#{arch}.#{ext}"
end
defp dagger_bin_name(cli_version, :windows) do
@dagger_bin_prefix <> cli_version <> ".exe"
end
defp dagger_bin_name(cli_version, _) do
@dagger_bin_prefix <> cli_version
end
end