Serve Prometheus metrics on 9568, and unbreak the image build

704d6eb · Gabriel Morell · 2026-08-25 02:43

10 files +118 -2
Message
{commit_body(@commit)}

Files changed

modified Dockerfile
+15 −2
@@ -4,9 +4,22 @@
4 4 # - https://hub.docker.com/r/hexpm/elixir/tags
5 5 # - https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
6 6 #
7 +# OTP 27.3.4.16, not 27.2. 27.2's TLS stack rejects the certificate chain
8 +# behind builds.hex.pm with `key_usage_mismatch` — it refuses a chain cert
9 +# carrying both keyUsage(keyCertSign,cRLSign) and extendedKeyUsage(serverAuth)
10 +# — so `mix local.hex --force` cannot download Hex and the image build dies
11 +# before any of this project's code is involved. muddle hit this first; both
12 +# repos were pinned to the same image.
13 +#
14 +# Reproduced and fixed against the real images:
15 +# 27.2 -> "Could not install Hex ... could not download metadata"
16 +# 27.3.4.16 -> installs hex 2.5.1 and rebar3 cleanly
17 +#
18 +# The Debian date moves with it because 27.3.4.16 is not published against
19 +# bookworm-20241223; that also bumps the runner, which is overdue anyway.
7 20 ARG ELIXIR_VERSION=1.17.3
8 −ARG OTP_VERSION=27.2
9 −ARG DEBIAN_VERSION=bookworm-20241223-slim
21 +ARG OTP_VERSION=27.3.4.16
22 +ARG DEBIAN_VERSION=bookworm-20260803-slim
10 23
11 24 ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
12 25 ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
modified config/config.exs
+14 −0
@@ -98,6 +98,20 @@ config :sentry,
98 98 # libcluster topology is configured in runtime.exs (prod-only) so it
99 99 # never tries to talk to the k8s API in dev/test.
100 100
101 +# Prometheus metrics, exposed on port 9568 by Diogramos.MetricsPlug and scraped
102 +# from the pod annotations in argo-01-bos/diogramos/app.yml.
103 +#
104 +# `grafana: :disabled` is load-bearing, not boilerplate: PromEx would otherwise
105 +# upload its own dashboards on boot, and the dashboards for this fleet are
106 +# committed in deployment/grafana and pushed from there. `metrics_server:
107 +# :disabled` because that option is Cowboy-based — see Diogramos.MetricsPlug.
108 +config :diogramos, Diogramos.PromEx,
109 + disabled: false,
110 + manual_metrics_start_delay: :no_delay,
111 + drop_metrics_groups: [],
112 + grafana: :disabled,
113 + metrics_server: :disabled
114 +
101 115 # Import environment specific config. This must remain at the bottom
102 116 # of this file so it overrides the configuration defined above.
103 117 import_config "#{config_env()}.exs"
modified lib/diogramos/application.ex
+3 −0
@@ -17,6 +17,9 @@ defmodule Diogramos.Application do
17 17 children =
18 18 [
19 19 DiogramosWeb.Telemetry,
20 + Diogramos.PromEx,
21 + # The metrics listener, on its own port — see Diogramos.MetricsPlug.
22 + {Bandit, plug: Diogramos.MetricsPlug, scheme: :http, port: 9568},
20 23 Diogramos.Repo
21 24 ] ++
22 25 cluster_children(topologies) ++
added lib/diogramos/metrics_plug.ex
+27 −0
@@ -0,0 +1,27 @@
1 +defmodule Diogramos.MetricsPlug do
2 + @moduledoc """
3 + Serves `/metrics` on port 9568, separately from the site.
4 +
5 + Not mounted in the endpoint on purpose. The Ingress matches `/` as a prefix,
6 + so anything in the Phoenix router is reachable from the internet — that is
7 + how the Rust services ended up serving `/metrics` publicly. A second listener
8 + on a port the Ingress does not name keeps it to in-cluster scrapers, and the
9 + scrape annotations on the pod point here rather than at 4000.
10 +
11 + This runs on Bandit, the same server the endpoint uses. PromEx ships its own
12 + `metrics_server` option which would do the same job, but it is built on
13 + Plug.Cowboy — using it would mean compiling and running Cowboy alongside
14 + Bandit for one route.
15 + """
16 +
17 + use Plug.Builder
18 +
19 + plug PromEx.Plug, prom_ex_module: Diogramos.PromEx
20 +
21 + # `PromEx.Plug` passes anything that is not /metrics straight through rather
22 + # than halting, so without this the listener would answer every other path
23 + # with 200 and an empty body.
24 + plug :not_found
25 +
26 + defp not_found(conn, _opts), do: send_resp(conn, 404, "")
27 +end
added lib/diogramos/prom_ex.ex
+49 −0
@@ -0,0 +1,49 @@
1 +defmodule Diogramos.PromEx do
2 + @moduledoc """
3 + Prometheus metrics for diogramos, served on port 9568 by `Diogramos.MetricsPlug`.
4 +
5 + ## Why the prefix is `phx` rather than `diogramos`
6 +
7 + PromEx names metrics `[otp_app, :prom_ex, plugin]` by default, which would
8 + give `diogramos_prom_ex_phoenix_http_requests_total` here and a *different*
9 + name in every other Phoenix app in this fleet — six near-identical apps that
10 + no single dashboard could describe.
11 +
12 + Every plugin below overrides that to `[:phx, plugin]`, so all six emit
13 + identical names and the Kubernetes `namespace` label is what tells them
14 + apart. That is the same shape the Rust services have, where one dashboard is
15 + cloned per app with nothing changed but the namespace. Two apps sharing a
16 + namespace would collide; none do.
17 +
18 + ## Grafana upload is off
19 +
20 + PromEx can push its own bundled dashboards to Grafana on boot. That would
21 + fight `deployment/grafana`, where git is the source of truth and `sync.sh`
22 + pushes dashboards the other way — an uploaded dashboard would either be
23 + reverted by the next sync or quietly win over the committed one. Disabled
24 + explicitly in `config/config.exs` rather than left to the default, because
25 + this is the kind of thing that gets turned on by a copied config snippet.
26 + """
27 +
28 + use PromEx, otp_app: :diogramos
29 +
30 + alias PromEx.Plugins
31 +
32 + @impl true
33 + def plugins do
34 + [
35 + # Uptime and build info, so a restart is visible without reading the
36 + # process metrics.
37 + {Plugins.Application, metric_prefix: [:phx, :application]},
38 + # The BEAM itself: run queue, memory by kind, process and port counts.
39 + # This is most of what is worth knowing about an Elixir app and none of
40 + # it is visible from the outside.
41 + {Plugins.Beam, metric_prefix: [:phx, :beam]},
42 + # `path` is the *route pattern* from the router, not the request path, so
43 + # the label stays bounded no matter how many distinct URLs are hit.
44 + {Plugins.Phoenix,
45 + router: DiogramosWeb.Router, endpoint: DiogramosWeb.Endpoint, metric_prefix: [:phx, :phoenix]},
46 + {Plugins.Ecto, repos: [Diogramos.Repo], metric_prefix: [:phx, :ecto]}
47 + ]
48 + end
49 +end
modified mix.exs
+5 −0
@@ -64,6 +64,11 @@ defmodule Diogramos.MixProject do
64 64 {:req, "~> 0.5"},
65 65 {:telemetry_metrics, "~> 1.0"},
66 66 {:telemetry_poller, "~> 1.0"},
67 + # Pinned to a minor, not just a major: the metric names this app shares
68 + # with the rest of the Phoenix fleet are what makes one dashboard shape
69 + # describe all of them, and a plugin rename between minors would only
70 + # show up as an empty panel.
71 + {:prom_ex, "~> 1.11.0"},
67 72 {:gettext, "~> 1.0"},
68 73 {:jason, "~> 1.2"},
69 74 {:dns_cluster, "~> 0.2.0"},
modified mix.lock
+5 −0
@@ -1,6 +1,7 @@
1 1 %{
2 2 "bandit": {:hex, :bandit, "1.10.4", "02b9734c67c5916a008e7eb7e2ba68aaea6f8177094a5f8d95f1fb99069aac17", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "a5faf501042ac1f31d736d9d4a813b3db4ef812e634583b6a457b0928798a51d"},
3 3 "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
4 + "castore": {:hex, :castore, "1.0.21", "0a0e8330dc267a40a3b7ad86d39302764bb71758172904e6a59d5ad6443ce307", [:mix], [], "hexpm", "e42e22723e25dbd46876d056a03f685513d6e98f6b5e555dc551321decd76c5c"},
4 5 "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
5 6 "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
6 7 "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
@@ -35,7 +36,9 @@
35 36 "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
36 37 "nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"},
37 38 "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
39 + "octo_fetch": {:hex, :octo_fetch, "0.5.0", "f50701568b9fc752656367f82cc134d5fbefff37c5a0e8ddfcceb02ceee3f5fc", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6226cc3c14ca948ee9f25fb0446322e5c288e215da9beba7899b6b5f4cd3ccb0"},
38 40 "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
41 + "peep": {:hex, :peep, "3.5.0", "9f6ead7b0f2c684494200c8fc02e7e62e8c459afe861b29bd859e4c96f402ed8", [:mix], [{:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:plug, "~> 1.16", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5a73a99c6e60062415efeb7e536a663387146463a3d3df1417da31fd665ac210"},
39 42 "phoenix": {:hex, :phoenix, "1.8.5", "919db335247e6d4891764dc3063415b0d2457641c5f9b3751b5df03d8e20bbcf", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "83b2bb125127e02e9f475c8e3e92736325b5b01b0b9b05407bcb4083b7a32485"},
40 43 "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
41 44 "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
@@ -47,6 +50,7 @@
47 50 "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
48 51 "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
49 52 "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"},
53 + "prom_ex": {:hex, :prom_ex, "1.11.0", "1f6d67f2dead92224cb4f59beb3e4d319257c5728d9638b4a5e8ceb51a4f9c7e", [:mix], [{:absinthe, ">= 1.7.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.1.0", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.11.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.10.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.4", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:peep, "~> 3.0", [hex: :peep, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.7.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.20.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.16.0", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 2.6.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.1", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "76b074bc3730f0802978a7eb5c7091a65473eaaf07e99ec9e933138dcc327805"},
50 54 "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
51 55 "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
52 56 "sentry": {:hex, :sentry, "10.10.0", "d058b635f3796947545c8057a42996f6dbefd12152da947209b56d16af41b161", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0 or ~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c7ddd3cfdd63fcee53b1e28f9a653037e6927b2b1dbd300b7aeee9687c7a8f6"},
@@ -55,6 +59,7 @@
55 59 "tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"},
56 60 "telemetry": {:hex, :telemetry, "1.4.1", "ab6de178e2b29b58e8256b92b382ea3f590a47152ca3651ea857a6cae05ac423", [:rebar3], [], "hexpm", "2172e05a27531d3d31dd9782841065c50dd5c3c7699d95266b2edd54c2dafa1c"},
57 61 "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
62 + "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.1", "c9755987d7b959b557084e6990990cb96a50d6482c683fb9622a63837f3cd3d8", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5e2c599da4983c4f88a33e9571f1458bf98b0cf6ba930f1dc3a6e8cf45d5afb6"},
58 63 "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
59 64 "thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
60 65 "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},

Parents: 0e44309