Packages

Verified OCI/Docker registry client library for Elixir

Current section

Files

Jump to
regc lib regc oci platform matcher.ex
Raw

lib/regc/oci/platform/matcher.ex

defmodule Regc.Oci.Platform.Matcher do
@moduledoc false
alias Regc.Oci.{Error, Reference}
alias Regc.Oci.Descriptor.Preference
alias Regc.Oci.Platform.Compatibility
@spec select([map()], map(), Reference.t() | nil, (term() ->
{:ok, map() | nil} | {:error, term()})) ::
{:ok, map()} | {:error, Error.t()}
def select(descriptors, requested, reference, normalize_platform)
when is_list(descriptors) and is_function(normalize_platform, 1) do
select(descriptors, requested, reference, normalize_platform, :exact)
end
@spec select(
[map()],
map(),
Reference.t() | nil,
(term() -> {:ok, map() | nil} | {:error, term()}),
:exact | :compatible_best
) :: {:ok, map()} | {:error, Error.t()}
def select(descriptors, requested, reference, normalize_platform, :exact)
when is_list(descriptors) and is_function(normalize_platform, 1) do
matches =
Enum.filter(descriptors, &matches?(&1.platform, requested, normalize_platform))
case unique_descriptors(matches, normalize_platform) do
[] ->
error(
:platform_not_found,
"no manifest matches #{format(requested)}",
reference
)
[descriptor] ->
{:ok, descriptor}
_matches ->
error(
:ambiguous_platform,
"multiple manifests match #{format(requested)}; specify platform qualifiers",
reference
)
end
end
def select(descriptors, requested, reference, normalize_platform, :compatible_best)
when is_list(descriptors) and is_function(normalize_platform, 1) do
descriptors
|> unique_valid_descriptors(normalize_platform)
|> Enum.reduce(nil, fn descriptor, best ->
{:ok, target} = normalize_platform.(descriptor.platform)
previous = if is_nil(best), do: nil, else: elem(best, 1)
if Compatibility.better?(requested, target, previous),
do: {descriptor, target},
else: best
end)
|> case do
nil ->
error(
:platform_not_found,
"no compatible manifest matches #{format(requested)}",
reference
)
{descriptor, _platform} ->
{:ok, descriptor}
end
end
@spec config_matches?(map(), map()) :: boolean()
def config_matches?(actual, requested) do
actual.os == requested.os and actual.architecture == requested.architecture and
optional_value_matches?(requested.variant, actual.variant) and
optional_value_matches?(requested.os_version, actual.os_version) and
feature_list_matches?(requested.os_features, actual.os_features)
end
@spec details(map()) :: map()
def details(platform) do
%{
os: platform.os,
architecture: platform.architecture,
variant: platform.variant,
os_version: platform.os_version,
os_features: platform.os_features,
features: platform.features
}
end
defp unique_descriptors(descriptors, normalize_platform) do
{order, unique} =
Enum.reduce(descriptors, {[], %{}}, fn descriptor, {order, unique} ->
identity = descriptor_identity(descriptor, normalize_platform)
if Map.has_key?(unique, identity) do
{order, Map.update!(unique, identity, &Preference.prefer_inline(&1, descriptor))}
else
{[identity | order], Map.put(unique, identity, descriptor)}
end
end)
order
|> Enum.reverse()
|> Enum.map(&Map.fetch!(unique, &1))
end
defp unique_valid_descriptors(descriptors, normalize_platform) do
descriptors
|> Enum.filter(fn descriptor ->
match?({:ok, %Regc.Oci.Platform{}}, normalize_platform.(descriptor.platform))
end)
|> unique_descriptors(normalize_platform)
end
defp descriptor_identity(descriptor, normalize_platform) when is_map(descriptor) do
{
Map.fetch!(descriptor, :media_type),
Map.fetch!(descriptor, :digest),
Map.fetch!(descriptor, :size),
Map.get(descriptor, :artifact_type),
platform_identity(Map.get(descriptor, :platform), normalize_platform)
}
end
defp platform_identity(platform, normalize_platform) do
{:ok, parsed} = normalize_platform.(platform)
details(parsed)
end
defp matches?(platform, requested, normalize_platform) do
case normalize_platform.(platform) do
{:ok, actual} when not is_nil(actual) -> platform_matches?(actual, requested)
{:ok, nil} -> false
{:error, _reason} -> false
end
end
defp platform_matches?(actual, requested) do
config_matches?(actual, requested) and
feature_list_matches?(requested.features, actual.features)
end
defp optional_value_matches?(nil, _actual), do: true
defp optional_value_matches?(requested, actual), do: requested == actual
defp feature_list_matches?([], _actual), do: true
defp feature_list_matches?(requested, actual), do: requested == actual
defp format(platform) do
base =
platform.os <>
"/" <>
platform.architecture <>
if(platform.variant, do: "/#{platform.variant}", else: "")
qualifiers =
[
if(platform.os_version, do: "os.version=#{platform.os_version}"),
if(platform.os_features != [], do: "os.features=#{Enum.join(platform.os_features, ",")}"),
if(platform.features != [], do: "features=#{Enum.join(platform.features, ",")}")
]
|> Enum.reject(&is_nil/1)
if qualifiers == [], do: base, else: base <> " (" <> Enum.join(qualifiers, "; ") <> ")"
end
defp error(code, message, reference) do
Error.result(:platform, code, message, reference: reference)
end
end