Current section
Files
Jump to
Current section
Files
src/relsync_cli.erl
%% Copyright 2020 João Henrique Ferreira de Freitas
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(relsync_cli).
-export([
do/3,
opts/0
]).
-include("relsync.hrl").
-type parsed() ::
{undefined, config()} | {ssh, config()} | {erl, config()} | help | version | {error, string()}.
-spec opts() -> [getopt:option_spec()].
opts() ->
[
%% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
{destnode, undefined, "destination-node", {string, "node@other"}, "Destination node"},
{destpath, undefined, "destination-path", {string, "/srv/erlang"},
"Path to release on the destination (Can be on a read-only filesystem)"},
{destrwpath, undefined, "destination-rw-path", {string, ""},
"Path to writable location on the destination (empty if not needed)"},
{localpath, undefined, "local-path", string, "Path to local release"},
{hooks, undefined, "hooks", string,
"Erlang module containing hooks to run on the destination"},
{exclude_system_libs, undefined, "exclude-system-libs", {boolean, true},
"Do not synchronize system libs"},
{cookie, undefined, "cookie", {string, "cookie"}, "Erlang magic cookie to use"},
{sname, undefined, "sname", string, "Short name for the local node"},
{name, undefined, "name", string, "Long name for the local node"},
{mode, undefined, "mode", {string, "erl"},
"Method to synchronize release using ssh or erlang distributed protocol"},
{port, undefined, "port", integer, "ssh port to use"},
{user, undefined, "user", string, "ssh username"},
{help, undefined, "help", undefined, "Show help usage"}
].
do(Name, ArgOptions, ConfigOptions) ->
ArgParsed = parse_opts(ArgOptions),
ConfigParsed = parse_opts(ConfigOptions),
Options = resolve_parsed(ArgParsed, ConfigParsed),
case Options of
help ->
getopt:usage(relsync_cli:opts(), Name),
erlang:halt(0);
{ssh, Config} ->
ok = relsync:update_nodes(ssh, Config);
{erl, Config} ->
target_syncer_sup:start_link(),
ok = relsync:setup_local_node(Config),
ok = relsync:update_nodes(erl, Config);
{error, Message} ->
io:put_chars(standard_error, [Message, "\n\n"]),
getopt:usage(relsync_cli:opts(), Name),
erlang:halt(2)
end.
-spec parse_opts(list()) -> parsed().
parse_opts(Args) ->
parse_opts(Args, undefined, #config{}).
parse_opts([help | _Rest], _, _) ->
help;
parse_opts([version | _Rest], _, _) ->
version;
parse_opts([{mode, "ssh"} | Rest], undefined, Config) ->
parse_opts(Rest, ssh, Config#config{mode = ssh});
parse_opts([{mode, "erl"} | Rest], undefined, Config) ->
parse_opts(Rest, erl, Config#config{mode = erl});
parse_opts([{exclude_system_libs, Flag} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{exclude_system_libs = Flag});
parse_opts([{destnode, Node} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{destnode = Node});
parse_opts([{destpath, Path} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{destpath = Path});
parse_opts([{destrwpath, Path} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{destrwpath = Path});
parse_opts([{localpath, Path} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{localpath = Path});
parse_opts([{hooks, Hooks} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{hooks = Hooks});
parse_opts([{port, Port} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{port = Port});
parse_opts([{user, User} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{user = User});
parse_opts([{cookie, Cookie} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{cookie = Cookie});
parse_opts([{sname, Name} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{sname = Name});
parse_opts([{name, Name} | Rest], Opt, Config) ->
parse_opts(Rest, Opt, Config#config{name = Name});
parse_opts([], Mode, Config) ->
{Mode, Config};
parse_opts([Unknown | _], _, _) ->
{error, io_lib:format("unknown option: ~p", [Unknown])}.
-spec resolve_parsed(parsed(), parsed()) -> parsed().
resolve_parsed(ArgParsed, ConfigParsed) ->
case {ArgParsed, ConfigParsed} of
{{error, _} = Error, _} -> Error;
{_, {error, _} = Error} -> Error;
{help, _} -> help;
{_, help} -> help;
{version, _} -> version;
{_, version} -> version;
{{ssh, _}, {erl, _}} -> {error, "either ssh or erl should be provide"};
{{erl, _}, {ssh, _}} -> {error, "either ssh or erl should be provide"};
{{undefined, _}, {undefined, _}} -> {error, "either ssh or erl should be provide"};
{{ssh, C0}, {ssh, C1}} -> {ssh, resolve_config(C0, C1)};
{{erl, C0}, {erl, C1}} -> {erl, resolve_config(C0, C1)};
{{ssh, C0}, {undefined, C1}} -> {ssh, resolve_config(C0, C1)};
{{undefined, C0}, {ssh, C1}} -> {ssh, resolve_config(C0, C1)};
{{erl, C0}, {undefined, C1}} -> {erl, resolve_config(C0, C1)};
{{undefined, C0}, {erl, C1}} -> {erl, resolve_config(C0, C1)}
end.
resolve_config(
#config{
cookie = ArgsCookie,
sname = ArgsSName,
name = ArgsName,
port = ArgsPort,
user = ArgsUser,
destnode = ArgsDestNode,
destpath = ArgsDestPath,
destrwpath = ArgsDestRWPath,
localpath = ArgsLocalPath,
hooks = ArgsHooks,
exclude_system_libs = ArgsExcludeSystemLibs
},
#config{
cookie = OptsCookie,
sname = OptsSName,
name = OptsName,
port = OptsPort,
user = OptsUser,
destnode = OptsDestNode,
destpath = OptsDestPath,
destrwpath = OptsDestRWPath,
localpath = OptsLocalPath,
hooks = OptsHooks,
exclude_system_libs = OptsExcludeSystemLibs
}
) ->
#config{
cookie = resolve_cookie(ArgsCookie, OptsCookie),
sname = resolve_sname(ArgsSName, OptsSName),
name = resolve_name(ArgsName, OptsName),
port = resolve_port(ArgsPort, OptsPort),
user = resolve_user(ArgsUser, OptsUser),
destnode = resolve_destnode(ArgsDestNode, OptsDestNode),
destpath = resolve_destpath(ArgsDestPath, OptsDestPath),
destrwpath = resolve_destrwpath(ArgsDestRWPath, OptsDestRWPath),
localpath = resolve_localpath(ArgsLocalPath, OptsLocalPath),
hooks = resolve_hooks(ArgsHooks, OptsHooks),
exclude_system_libs = resolve_exclude_system_libs(
ArgsExcludeSystemLibs,
OptsExcludeSystemLibs
)
}.
resolve_cookie(undefined, C) -> C;
resolve_cookie(C, _) -> C.
resolve_sname(undefined, S) -> S;
resolve_sname(S, _) -> S.
resolve_name(undefined, N) -> N;
resolve_name(N, _) -> N.
resolve_port(0, P) -> P;
resolve_port(P, _) -> P.
resolve_user(undefined, U) -> U;
resolve_user(U, _) -> U.
resolve_destnode(undefined, D) -> D;
resolve_destnode(D, _) -> D.
resolve_destpath(undefined, D) -> D;
resolve_destpath(D, _) -> D.
resolve_destrwpath(undefined, D) -> D;
resolve_destrwpath(D, _) -> D.
resolve_localpath(undefined, L) -> L;
resolve_localpath(L, _) -> L.
resolve_hooks(undefined, H) -> H;
resolve_hooks(H, _) -> H.
resolve_exclude_system_libs(true, E) -> E;
resolve_exclude_system_libs(E, _) -> E.