diff --git a/README.md b/README.md index 0aac93f..4db90a8 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,28 @@ The following table summarizes the default versions: Please note that you can use any version you want by customizing your dockerfiles. See `mix docker.customize` for reference. +#### How to provide identity so mix can fetch dependencies from git repositories using ssh? +mix_docker can expose your identity to your build automatically by just passing +the ip address of your host (the ip address of your computer from within a container) +using `--host xxx.xxx.xxx.xxx` + +For example: + +```bash +mix docker.build --host 10.200.10.1 +``` + +The host ip address varies depending on your OS. +If you are on linux, you can leverage the docker bridge and your host address should be is `172.17.0.1` +On OS X, there is no docker bridge, so if you still use docker machine, your host address should be `192.168.99.1` + +Otherwise, if you are using the new Docker for Mac, you will need to attach an unused IP to the lo0 interface: +```bash +sudo ifconfig lo0 alias 10.200.10.1/24 +``` + +By default, mix_docker is providing your current user identity: `~/.ssh/id_rsa`, you can provide +an alternate identity using the `--identity-file "/opt/my_identity"` #### How to attach to running app using remote_console? diff --git a/lib/identity_plug.ex b/lib/identity_plug.ex new file mode 100644 index 0000000..49956b7 --- /dev/null +++ b/lib/identity_plug.ex @@ -0,0 +1,25 @@ +defmodule MixDocker.IdentityPlug do + import Plug.Conn + alias Plug.Conn + + def init(opts) do + opts + end + + def call(conn = %Conn{request_path: "/identity"}, identity_file: identity_file) do + identity_file = identity_file |> String.replace("~", System.user_home) + case File.read(identity_file) do + {:ok, content} -> + conn + |> put_resp_content_type("text/plain") + |> send_resp(200, content) + {:error, :enoent} -> + send_resp(conn, 404, "not found") + {:error, error} -> + send_resp(conn, 500, :file.format_error(error)) + end + end + def call(conn, _) do + send_resp(conn, 404, "not found") + end +end \ No newline at end of file diff --git a/lib/mix_docker.ex b/lib/mix_docker.ex index 8e0fa56..5d6708e 100644 --- a/lib/mix_docker.ex +++ b/lib/mix_docker.ex @@ -17,10 +17,25 @@ defmodule MixDocker do end def build(args) do + {opts, args} = extract_opts(args) + build(args, opts) + end + def build(args, opts) do + {ip, identity_file} = ssh_identity_opts(opts) + opts = + if ip != nil do + Plug.Adapters.Cowboy.http MixDocker.IdentityPlug, [identity_file: identity_file], ip: ip + host = Keyword.fetch!(opts, :host) + Keyword.update(opts, :build_arg, "host=" <> host, &(&1 <> ",host=" <> host)) + else + opts + end + with_dockerfile @dockerfile_build, fn -> - docker :build, @dockerfile_build, image(:build), args + docker :build, @dockerfile_build, image(:build), (if Keyword.has_key?(opts, :build_arg), do: args ++ ["--build-arg", Keyword.fetch!(opts, :build_arg)], else: args) end + if ip != nil, do: Plug.Adapters.Cowboy.shutdown MixDocker.Plug.HTTP Mix.shell.info "Docker image #{image(:build)} has been successfully created" end @@ -59,7 +74,7 @@ defmodule MixDocker do def shipit(args) do {opts, args} = extract_opts(args) - build(args) + build(args, opts) release(args) publish(args, opts) end @@ -112,9 +127,20 @@ defmodule MixDocker do # Simple recursive extraction instead of OptionParser to keep other (docker) flags intact defp extract_opts(args), do: extract_opts([], args, []) defp extract_opts(head, ["--tag", tag | tail], opts), do: extract_opts(head, tail, Keyword.put(opts, :tag, tag)) + defp extract_opts(head, ["--build-arg", build_arg | tail], opts), do: extract_opts(head, tail, Keyword.put(opts, :build_arg, build_arg)) + defp extract_opts(head, ["--host", host | tail], opts), do: extract_opts(head, tail, Keyword.put(opts, :host, host)) + defp extract_opts(head, ["--identity-file", identity_file | tail], opts), do: extract_opts(head, tail, Keyword.put(opts, :identity_file, identity_file)) defp extract_opts(head, [], opts), do: {opts, head} defp extract_opts(head, [h | tail], opts), do: extract_opts(head ++ [h], tail, opts) + defp ssh_identity_opts(opts) do + with {:ok, host} <- Keyword.fetch(opts, :host), + {:ok, ip} <- host |> String.to_char_list |> :inet_parse.address do + {ip, Keyword.get(opts, :identity_file, "~/.ssh/id_rsa")} + else + _ -> {nil, nil} + end + end defp docker(:cp, cid, source, dest) do system! "docker", ["cp", "#{cid}:#{source}", dest] diff --git a/mix.exs b/mix.exs index 4518bb9..582a55d 100644 --- a/mix.exs +++ b/mix.exs @@ -31,12 +31,14 @@ defmodule MixDocker.Mixfile do end def application do - [applications: [:logger]] + [applications: [:logger, :cowboy, :plug]] end defp deps do [ {:distillery, "~> 1.2"}, + {:cowboy, "~> 1.0.0"}, + {:plug, "~> 1.0"}, {:ex_doc, "~> 0.10", only: :dev} ] end diff --git a/mix.lock b/mix.lock index 7a06bc8..558d549 100644 --- a/mix.lock +++ b/mix.lock @@ -1,3 +1,8 @@ -%{"distillery": {:hex, :distillery, "1.3.3", "fe6f8f730d61f69e78408c8db6fb9909ae782803c7a03d2abf4465bfe2e408e9", [:mix], []}, +%{"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, + "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, + "distillery": {:hex, :distillery, "1.3.3", "fe6f8f730d61f69e78408c8db6fb9909ae782803c7a03d2abf4465bfe2e408e9", [:mix], []}, "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []}, - "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}} + "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, + "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []}, + "plug": {:hex, :plug, "1.3.3", "d9be189924379b4e9d470caef87380d09549aea1ceafe6a0d41292c8c317c923", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, + "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []}} diff --git a/priv/Dockerfile.build b/priv/Dockerfile.build index 7f2feaf..333de1a 100644 --- a/priv/Dockerfile.build +++ b/priv/Dockerfile.build @@ -7,7 +7,7 @@ RUN \ echo "@edge http://nl.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \ apk update && \ apk --no-cache --update add \ - git make g++ \ + git openssh-client wget make g++ \ elixir@edge=1.4.2-r0 && \ rm -rf /var/cache/apk/* @@ -23,7 +23,21 @@ ENV MIX_ENV=prod RUN mkdir config COPY config/* config/ COPY mix.exs mix.lock ./ -RUN mix do deps.get, deps.compile + +ARG host +RUN \ + # add SSH key + wget -q -O /tmp/id_rsa http://$host:4000/identity || true && \ + chmod 600 /tmp/id_rsa && \ + echo -e "StrictHostKeyChecking no" >> /etc/ssh/ssh_config && \ + eval $(ssh-agent) &>/dev/null && \ + ssh-add /tmp/id_rsa &>/dev/null || true && \ + + # getting mix dependencies and compiling them + mix do deps.get, deps.compile && \ + + # cleanup + rm /tmp/id_rsa COPY . .