Skip to content

Commit

Permalink
Fetch docker release image from API
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed Sep 29, 2022
1 parent a01d10b commit 7f07759
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 12 deletions.
95 changes: 87 additions & 8 deletions lib/mix/tasks/phx.gen.release.ex
Expand Up @@ -35,6 +35,8 @@ defmodule Mix.Tasks.Phx.Gen.Release do

use Mix.Task

require Logger

@doc false
def run(args) do
opts = parse_args(args)
Expand All @@ -54,8 +56,6 @@ defmodule Mix.Tasks.Phx.Gen.Release do
binding = [
app_namespace: app_namespace,
otp_app: app,
elixir_vsn: System.version(),
otp_vsn: otp_vsn(),
assets_dir_exists?: File.dir?("assets")
]

Expand All @@ -73,10 +73,7 @@ defmodule Mix.Tasks.Phx.Gen.Release do
end

if opts.docker do
Mix.Phoenix.copy_from(paths(), "priv/templates/phx.gen.release", binding, [
{:eex, "Dockerfile.eex", "Dockerfile"},
{:eex, "dockerignore.eex", ".dockerignore"}
])
gen_docker(binding)
end

File.chmod!("rel/overlays/bin/server", 0o755)
Expand Down Expand Up @@ -189,6 +186,90 @@ defmodule Mix.Tasks.Phx.Gen.Release do
end
end

defp ecto_sql_installed?, do: Mix.Project.deps_paths() |> Map.has_key?(:ecto_sql)

@debian "bullseye"
defp gen_docker(binding) do
elixir_vsn = System.version()
otp_vsn = otp_vsn()

url =
"https://hub.docker.com/v2/namespaces/hexpm/repositories/elixir/tags?name=#{elixir_vsn}-erlang-#{otp_vsn}-debian-#{@debian}-"

debian_vsn =
fetch_body!(url)
|> Phoenix.json_library().decode!()
|> Map.fetch!("results")
|> Enum.find_value(:error, fn %{"name" => name} ->
if String.ends_with?(name, "-slim") do
%{"vsn" => vsn} = Regex.named_captures(~r/.*debian-#{@debian}-(?<vsn>.*)-slim/, name)
{:ok, vsn}
end
end)

case debian_vsn do
{:ok, debian_vsn} ->
binding =
Keyword.merge(binding,
debian: @debian,
debian_vsn: debian_vsn,
elixir_vsn: elixir_vsn,
otp_vsn: otp_vsn
)

Mix.Phoenix.copy_from(paths(), "priv/templates/phx.gen.release", binding, [
{:eex, "Dockerfile.eex", "Dockerfile"},
{:eex, "dockerignore.eex", ".dockerignore"}
])

:error ->
raise "unable to fetch supported Docker image for Elixir #{elixir_vsn} and Erlang #{otp_vsn}"
end
end

defp fetch_body!(url) do
url = String.to_charlist(url)
Logger.debug("Fetching latest image information from #{url}")

{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)

if proxy = System.get_env("HTTP_PROXY") || System.get_env("http_proxy") do
Logger.debug("Using HTTP_PROXY: #{proxy}")
%{host: host, port: port} = URI.parse(proxy)
:httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}])
end

if proxy = System.get_env("HTTPS_PROXY") || System.get_env("https_proxy") do
Logger.debug("Using HTTPS_PROXY: #{proxy}")
%{host: host, port: port} = URI.parse(proxy)
:httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}])
end

# https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets
http_options = [
ssl: [
verify: :verify_peer,
cacertfile: String.to_charlist(CAStore.file_path()),
depth: 3,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
],
versions: protocol_versions()
]
]

case :httpc.request(:get, {url, []}, http_options, body_format: :binary) do
{:ok, {{_, 200, _}, _headers, body}} -> body
other -> raise "couldn't fetch #{url}: #{inspect(other)}"
end
end

defp protocol_versions do
otp_major_vsn = :erlang.system_info(:otp_release) |> List.to_integer()
if otp_major_vsn < 25, do: [:"tlsv1.2"], else: [:"tlsv1.2", :"tlsv1.3"]
end

def otp_vsn do
major = to_string(:erlang.system_info(:otp_release))
path = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"])
Expand All @@ -202,6 +283,4 @@ defmodule Mix.Tasks.Phx.Gen.Release do
"#{major}.0"
end
end

defp ecto_sql_installed?, do: Mix.Project.deps_paths() |> Map.has_key?(:ecto_sql)
end
6 changes: 5 additions & 1 deletion mix.exs
Expand Up @@ -32,7 +32,9 @@ defmodule Phoenix.MixProject do
:ranch,
:cowboy_req,
Plug.Cowboy.Conn,
Plug.Cowboy
Plug.Cowboy,
:httpc,
:public_key
]
],
elixirc_paths: elixirc_paths(Mix.env()),
Expand Down Expand Up @@ -73,6 +75,8 @@ defmodule Phoenix.MixProject do
{:telemetry, "~> 0.4 or ~> 1.0"},
{:phoenix_pubsub, "~> 2.0"},
{:phoenix_view, "~> 1.0"},
# TODO drop castore when we require OTP 25+
{:castore, ">= 0.0.0"},

# Optional deps
{:plug_cowboy, "~> 2.2", optional: true},
Expand Down
6 changes: 3 additions & 3 deletions priv/templates/phx.gen.release/Dockerfile.eex
Expand Up @@ -8,13 +8,13 @@
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20220801-slim - for the release image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=<%= debian %>-<%= debian_vsn %>-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:<%= elixir_vsn %>-erlang-<%= otp_vsn %>-debian-bullseye-20210902-slim
# - Ex: hexpm/elixir:<%= elixir_vsn %>-erlang-<%= otp_vsn %>-debian-<%= debian %>-<%= debian_vsn %>-slim
#
ARG ELIXIR_VERSION=<%= elixir_vsn %>
ARG OTP_VERSION=<%= otp_vsn %>
ARG DEBIAN_VERSION=bullseye-20220801-slim
ARG DEBIAN_VERSION=<%= debian %>-<%= debian_vsn %>-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
Expand Down
2 changes: 2 additions & 0 deletions test/mix/tasks/phx.gen.release_test.exs
Expand Up @@ -5,6 +5,8 @@ defmodule Mix.Tasks.Phx.Gen.ReleaseTest do
import MixHelper
alias Mix.Tasks.Phx.Gen

@moduletag :capture_log

setup do
Mix.Task.clear()
:ok
Expand Down

0 comments on commit 7f07759

Please sign in to comment.