From 7a1b59015f4157e5fe9d98c13411ba953a5b1dfc Mon Sep 17 00:00:00 2001
From: Fred Hebert <mononcqc@ferd.ca>
Date: Mon, 21 Mar 2016 12:48:02 -0400
Subject: Extract dist config handling, support {dist, ...}

This commit moves the handling of distribution config and starting out
of rebar_prv_shell and into rebar_dist_utils. The module is able to
handle standard config options and boot a distributed node mode. This
could be used in plugins (once it is exposed) and other providers like
CT.

Configuration is also expanded so that options like:

    {dist, [{sname, atom()}, {name, atom()}, {setcookie, term()}]}

can be used and will be handled as a default. The config handler
supports similar terms from the command line being parsed in if the
calling provider supports them.

A test suite is added for configuration handling.
---
 src/rebar_dist_utils.erl        | 89 +++++++++++++++++++++++++++++++++++++++++
 src/rebar_prv_shell.erl         | 29 +-------------
 test/rebar_dist_utils_SUITE.erl | 74 ++++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 27 deletions(-)
 create mode 100644 src/rebar_dist_utils.erl
 create mode 100644 test/rebar_dist_utils_SUITE.erl

diff --git a/src/rebar_dist_utils.erl b/src/rebar_dist_utils.erl
new file mode 100644
index 0000000..141c89d
--- /dev/null
+++ b/src/rebar_dist_utils.erl
@@ -0,0 +1,89 @@
+%%% Common functions to boot/stop distributed setups for
+%%% the rebar3 script.
+-module(rebar_dist_utils).
+-export([either/3, short/2, long/2, find_options/1]).
+-include("rebar.hrl").
+
+%%%%%%%%%%%%%%%%%%
+%%% PUBLIC API %%%
+%%%%%%%%%%%%%%%%%%
+-spec either(Name::atom(), SName::atom(), Opts::[{setcookie,term()}]) -> atom().
+either(undefined, undefined, _) ->
+    'nonode@nohost';
+either(Name, undefined, Opts) ->
+    long(Name, Opts),
+    node();
+either(undefined, SName, Opts) ->
+    short(SName, Opts),
+    node();
+either(_, _, _) ->
+    ?ABORT("Cannot have both short and long node names defined", []).
+
+short(Name, Opts) ->
+    start(Name, shortnames, Opts).
+
+long(Name, Opts) ->
+    start(Name, longnames, Opts).
+
+-spec find_options(rebar_state:state()) -> {Long, Short, Opts} when
+      Long :: atom(),
+      Short :: atom(),
+      Opts :: [{setcookie,term()}].
+find_options(State) ->
+    {Long, Short} = find_name_options(State),
+    case find_cookie_option(State) of
+        nocookie ->
+            {Long, Short, []};
+        Cookie ->
+            {Long, Short, [{setcookie, Cookie}]}
+    end.
+
+%%%%%%%%%%%%%%%
+%%% PRIVATE %%%
+%%%%%%%%%%%%%%%
+start(Name, Type, Opts) ->
+    check_epmd(net_kernel:start([Name, Type])),
+    setup_cookie(Opts).
+
+check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) ->
+    ?ERROR("Erlang Distribution failed, falling back to nonode@nohost. "
+           "Verify that epmd is running and try again.",[]);
+check_epmd(_) ->
+    ok.
+
+setup_cookie(Opts) ->
+    case {node(), proplists:get_value(setcookie, Opts, nocookie)} of
+        {'nonode@nohost', _} -> nocookie;
+        {_, nocookie} -> nocookie;
+        {Node, Name} -> erlang:set_cookie(Node, Name)
+    end.
+
+find_name_options(State) ->
+    {Opts, _} = rebar_state:command_parsed_args(State),
+    %% First try the CLI
+    case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of
+        {undefined, undefined} ->
+            %% Else try the config file
+            DistOpts = rebar_state:get(State, dist, []),
+            %% Pick the first one seen to support profile merges
+            find_first_name(DistOpts);
+        Res ->
+            Res
+    end.
+
+find_first_name([]) -> {undefined, undefined};
+find_first_name([{sname,Val}|_]) -> {undefined, Val};
+find_first_name([{name,Val}|_]) -> {Val, undefined};
+find_first_name([_|Opts]) -> find_first_name(Opts).
+
+find_cookie_option(State) ->
+    {Opts, _} = rebar_state:command_parsed_args(State),
+    %% First try the CLI
+    case proplists:get_value(setcookie, Opts) of
+        undefined ->
+            %% Else try the config file
+            DistOpts = rebar_state:get(State, dist, []),
+            proplists:get_value(setcookie, DistOpts, nocookie);
+        Res ->
+            Res
+    end.
diff --git a/src/rebar_prv_shell.erl b/src/rebar_prv_shell.erl
index d67f940..365ed66 100644
--- a/src/rebar_prv_shell.erl
+++ b/src/rebar_prv_shell.erl
@@ -269,33 +269,8 @@ simulate_proc_lib() ->
     put('$initial_call', {rebar_agent, init, 1}).
 
 setup_name(State) ->
-    {Opts, _} = rebar_state:command_parsed_args(State),
-    case {proplists:get_value(name, Opts), proplists:get_value(sname, Opts)} of
-        {undefined, undefined} ->
-            ok;
-        {Name, undefined} ->
-            check_epmd(net_kernel:start([Name, longnames])),
-            setup_cookie(Opts);
-        {undefined, SName} ->
-            check_epmd(net_kernel:start([SName, shortnames])),
-            setup_cookie(Opts);
-        {_, _} ->
-            ?ABORT("Cannot have both short and long node names defined", [])
-    end.
-
-check_epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) ->
-    ?ERROR("Erlang Distribution failed, falling back to nonode@nohost. "
-           "Verify that epmd is running and try again.",[]);
-check_epmd(_) ->
-    ok.
-
-setup_cookie(Opts) ->
-    case {node(), proplists:get_value(setcookie, Opts, nocookie)} of
-        {'nonode@nohost', _} -> nocookie;
-        {_, nocookie} -> nocookie;
-        {Node, Name} -> erlang:set_cookie(Node, Name)
-    end.
-
+    {Long, Short, Opts} = rebar_dist_utils:find_options(State),
+    rebar_dist_utils:either(Long, Short, Opts).
 
 find_apps_to_boot(State) ->
     %% Try the shell_apps option
diff --git a/test/rebar_dist_utils_SUITE.erl b/test/rebar_dist_utils_SUITE.erl
new file mode 100644
index 0000000..f0a71be
--- /dev/null
+++ b/test/rebar_dist_utils_SUITE.erl
@@ -0,0 +1,74 @@
+%%% This suite currently only tests for options parsing since we do
+%%% not know if epmd will be running to actually boot nodes.
+-module(rebar_dist_utils_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-compile(export_all).
+
+all() -> [from_config, from_cli, overlap, from_config_profile].
+
+init_per_testcase(_, Config0) ->
+    Config = rebar_test_utils:init_rebar_state(Config0),
+    AppDir = ?config(apps, Config),
+    Name = rebar_test_utils:create_random_name("app_"),
+    Vsn = rebar_test_utils:create_random_vsn(),
+    rebar_test_utils:create_app(filename:join([AppDir,"apps",Name]), Name, Vsn, [kernel, stdlib]),
+    Config.
+
+
+end_per_testcase(_, _) ->
+    ok.
+
+from_config(Config) ->
+    ShortConfig = [{dist, [{sname, 'a@localhost'}, {setcookie, abc}]}],
+    LongConfig = [{dist, [{name, 'a@localhost.x'}, {setcookie, abc}]}],
+    BothConfig = [{dist, [{sname, 'a@localhost'}, {name, 'a@localhost.x'}, {setcookie,abc}]}],
+    NoConfig = [],
+    CookieConfig = [{dist, [{setcookie, def}]}],
+    NoCookie = [{dist, [{sname, 'a@localhost'}]}],
+    {ok, State0} = rebar_test_utils:run_and_check(Config, ShortConfig, ["version"], return),
+    {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State0),
+    {ok, State1} = rebar_test_utils:run_and_check(Config, LongConfig, ["version"], return),
+    {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State1),
+    %% only support the first name found, side-effect of wanting profile support
+    {ok, State2} = rebar_test_utils:run_and_check(Config, BothConfig, ["version"], return),
+    {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State2),
+    {ok, State3} = rebar_test_utils:run_and_check(Config, NoConfig, ["version"], return),
+    {undefined, undefined, []} = rebar_dist_utils:find_options(State3),
+    {ok, State4} = rebar_test_utils:run_and_check(Config, CookieConfig, ["version"], return),
+    {undefined, undefined, [{setcookie, def}]} = rebar_dist_utils:find_options(State4),
+    {ok, State5} = rebar_test_utils:run_and_check(Config, NoCookie, ["version"], return),
+    {undefined, 'a@localhost', []} = rebar_dist_utils:find_options(State5),
+    ok.
+
+from_cli(Config) ->
+    {ok, State0} = rebar_test_utils:run_and_check(Config, [], ["version"], return),
+    {undefined, undefined, []} = rebar_dist_utils:find_options(State0),
+    State1 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}, {setcookie,abc}], []}),
+    {undefined, 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State1),
+    State2 = rebar_state:command_parsed_args(State0, {[{name, 'a@localhost.x'}, {setcookie,abc}], []}),
+    {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State2),
+    State3 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}, {name, 'a@localhost.x'}, {setcookie,abc}], []}),
+    {'a@localhost.x', 'a@localhost', [{setcookie, abc}]} = rebar_dist_utils:find_options(State3),
+    State4 = rebar_state:command_parsed_args(State0, {[{setcookie,def}], []}),
+    {undefined, undefined, [{setcookie, def}]} = rebar_dist_utils:find_options(State4),
+    State5 = rebar_state:command_parsed_args(State0, {[{sname, 'a@localhost'}], []}),
+    {undefined, 'a@localhost', []} = rebar_dist_utils:find_options(State5),
+    ok.
+
+overlap(Config) ->
+    %% Make sure that CLI config takes over rebar config without clash for names, though
+    %% cookies can pass through
+    RebarConfig = [{dist, [{sname, 'a@localhost'}, {setcookie, abc}]}],
+    {ok, State0} = rebar_test_utils:run_and_check(Config, RebarConfig, ["version"], return),
+    State1 = rebar_state:command_parsed_args(State0, {[{name, 'b@localhost.x'}], []}),
+    {'b@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State1),
+    ok.
+
+from_config_profile(Config) ->
+    %% running as a profile does not create name clashes
+    RebarConfig = [{dist, [{sname, 'a@localhost'}, {setcookie, abc}]},
+                   {profiles, [ {fake, [{dist, [{name, 'a@localhost.x'}]}]} ]}],
+    {ok, State0} = rebar_test_utils:run_and_check(Config, RebarConfig, ["as","fake","version"], return),
+    {'a@localhost.x', undefined, [{setcookie, abc}]} = rebar_dist_utils:find_options(State0),
+    ok.
-- 
cgit v1.1