Skip to main content

lib/mix/tasks/stevedore.copy.ex

defmodule Mix.Tasks.Stevedore.Copy do
  @shortdoc "Copy an image between transports, preserving digests"

  @moduledoc """
  Copy an image from one transport to another.

      mix stevedore.copy SRC DST [options]

  SRC and DST are transport-prefixed references (`docker://`, `oci:`, `oci-archive:`,
  `docker-archive:`, `dir:`, `static:`).

  ## Options

    * `--all` — copy a whole multi-arch index
    * `--platform os/arch` — select one platform from an index
    * `--format oci|docker` — convert the manifest format
    * `--scheme http|https` — registry scheme (default https)

  ## Examples

      mix stevedore.copy docker://alpine:3.20 oci:./alpine:3.20
      mix stevedore.copy docker://alpine:3.20 docker://ghcr.io/me/alpine:3.20 --all
  """

  use Mix.Task

  alias Stevedore.CLI

  @switches [all: :boolean, platform: :string, format: :string, scheme: :string]

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

    case args do
      [src, dst] ->
        CLI.start_app()
        %{digest: digest} = CLI.unwrap!(Stevedore.copy(src, dst, copy_opts(opts)))
        Mix.shell().info(to_string(digest))

      _ ->
        Mix.raise(
          "usage: mix stevedore.copy SRC DST [--all --platform os/arch --format oci|docker]"
        )
    end
  end

  defp copy_opts(opts) do
    opts
    |> Keyword.take([:all, :platform, :scheme])
    |> maybe_format(opts[:format])
  end

  defp maybe_format(opts, nil), do: opts
  defp maybe_format(opts, "oci"), do: Keyword.put(opts, :format, :oci)
  defp maybe_format(opts, "docker"), do: Keyword.put(opts, :format, :docker)
  defp maybe_format(_opts, other), do: Mix.raise("unknown --format #{other} (use oci or docker)")
end