Packages

Verified OCI/Docker registry client library for Elixir

Current section

Files

Jump to
regc lib regc oci client index_resolver search.ex
Raw

lib/regc/oci/client/index_resolver/search.ex

defmodule Regc.Oci.Client.IndexResolver.Search do
@moduledoc false
alias Regc.Oci.{Descriptor, Error, MediaType, Reference}
alias Regc.Oci.Client.Resolution
alias Regc.Oci.Image.Selection
@enforce_keys [:remaining, :reference, :resolution, :not_found, :fetch]
defstruct [:remaining, :selected, :reference, :resolution, :not_found, :fetch]
@type result ::
{:ok, Selection.t() | nil, Resolution.t()}
| {:error, Error.t(), Resolution.t()}
@type fetch_candidate :: (Descriptor.t(), Resolution.t() -> result())
@spec run(
[Descriptor.t()],
Reference.t(),
Resolution.t(),
Error.t(),
fetch_candidate()
) :: result()
def run(descriptors, reference, resolution, not_found, fetch) do
state = %__MODULE__{
remaining: candidates(descriptors),
selected: nil,
reference: reference,
resolution: resolution,
not_found: not_found,
fetch: fetch
}
search(state)
end
defp search(%__MODULE__{remaining: [], selected: nil} = state) do
{:error, state.not_found, state.resolution}
end
defp search(%__MODULE__{remaining: [], selected: selection} = state) do
{:ok, selection, state.resolution}
end
defp search(%__MODULE__{remaining: [descriptor | remaining]} = state) do
case state.fetch.(descriptor, state.resolution) do
{:error, %Error{code: :platform_not_found}, branch_resolution} ->
search(%{
state
| remaining: remaining,
resolution: carry_budget(state, branch_resolution)
})
{:ok, selection, branch_resolution} ->
continue(
%{
state
| remaining: remaining,
resolution: carry_budget(state, branch_resolution)
},
selection
)
{:error, %Error{} = error, branch_resolution} ->
{:error, error, carry_budget(state, branch_resolution)}
end
end
defp continue(%__MODULE__{selected: nil} = state, selection) do
search(%{state | selected: selection})
end
defp continue(%__MODULE__{selected: selected} = state, selection) do
if Selection.same?(selected, selection) do
search(state)
else
{:error, ambiguity_error(state), state.resolution}
end
end
defp candidates(descriptors) do
descriptors
|> Enum.filter(fn descriptor ->
is_nil(descriptor.platform) and MediaType.index?(descriptor.media_type)
end)
|> unique_by_digest()
end
defp unique_by_digest(descriptors) do
{by_digest, order} =
Enum.reduce(descriptors, {%{}, []}, fn descriptor, {unique, order} ->
if Map.has_key?(unique, descriptor.digest) do
preferred =
Map.update!(unique, descriptor.digest, fn existing ->
Descriptor.prefer_inline(existing, descriptor)
end)
{preferred, order}
else
{Map.put(unique, descriptor.digest, descriptor), [descriptor.digest | order]}
end
end)
order
|> Enum.reverse()
|> Enum.map(&Map.fetch!(by_digest, &1))
end
defp carry_budget(state, branch_resolution) do
Resolution.carry_budget(state.resolution, branch_resolution)
end
defp ambiguity_error(state) do
platform = state.resolution.platform
Error.new(
:platform,
:ambiguous_platform,
"multiple manifests in nested indexes match #{platform.os}/#{platform.architecture}; specify platform qualifiers",
reference: state.reference
)
end
end