Skip to main content

lib/mix/tasks/stevedore.sync.ex

defmodule Mix.Tasks.Stevedore.Sync do
  @shortdoc "Bulk-copy images from a spec file"

  @moduledoc """
  Copy many images from a declarative spec file. Each non-blank line is `SRC DST` (lines starting
  with `#` are comments).

      mix stevedore.sync SPEC [--scheme http|https]

  ## Example spec

      docker://alpine:3.20   oci:./mirror/alpine:3.20
      docker://debian:12     oci:./mirror/debian:12
  """

  use Mix.Task

  alias Stevedore.CLI

  @switches [scheme: :string]

  @impl true
  def run(argv) do
    {opts, args, _} = OptionParser.parse(argv, switches: @switches)

    case args do
      [spec] ->
        CLI.start_app()
        jobs = spec |> File.read!() |> parse_jobs()
        {:ok, results} = Stevedore.sync(jobs, Keyword.take(opts, [:scheme]))
        Enum.each(results, &report/1)

      _ ->
        Mix.raise("usage: mix stevedore.sync SPEC")
    end
  end

  defp parse_jobs(contents) do
    for line <- String.split(contents, "\n", trim: true),
        trimmed = String.trim(line),
        trimmed != "" and not String.starts_with?(trimmed, "#"),
        [src, dst] <- [String.split(trimmed, ~r/\s+/, parts: 2)] do
      {src, dst}
    end
  end

  defp report({{src, dst}, {:ok, %{digest: digest}}}),
    do: Mix.shell().info("#{src} -> #{dst}  #{digest}")

  defp report({{src, dst}, {:error, reason}}),
    do: Mix.shell().error("#{src} -> #{dst}  FAILED: #{CLI.format_error(reason)}")
end