From 3bbe0ea1d1381b99a625fdd2f49896102865ee3d Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 1 Aug 2016 08:59:29 -0700 Subject: [PATCH 1/5] add support for including Windows INI-style config files --- priv/config/load_ini.config | 157 ++++++++++++++++++++++++++++++++ priv/test_config/backend.config | 16 ++-- priv/test_config/my_ini.config | 6 ++ 3 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 priv/config/load_ini.config create mode 100644 priv/test_config/my_ini.config diff --git a/priv/config/load_ini.config b/priv/config/load_ini.config new file mode 100644 index 0000000..7bde16c --- /dev/null +++ b/priv/config/load_ini.config @@ -0,0 +1,157 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*- +%% Copyright (C) 2016, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ + +%% Usage: From a setup .config file: +%% {ok, Instructions} = file:script("$PRIV/config/load_ini.config", +%% [{'FILE', YourINIfile}]) +%% Then, either append or prepend Instructions to the other config +%% instructions. +%% + +%% ================================================== +%% Mapping +%% ================================================== + +Str = fun(B) -> binary_to_list(B) end. + +ConfigMap = +[{<<"system">>, rvi_core, + [ + {<<"device_key" >> , {device_key , Str}}, + {<<"root_cert" >> , {root_cert , Str}}, + {<<"device_cert">> , {device_cert , Str}}, + {<<"cred_dir" >> , {cred_dir , Str}}, + {<<"node_address">> , {node_address , Str}}, + {<<"node_service_prefix">>, {node_service_prefix, Str}}, + {<<"node_id">> , {node_id , Str}} + ]} +]. + +%% This fun converts to a 'set_env' list, or raise an error +%% +ApplyMap = +fun(Grp, K, V) -> + case lists:keyfind(Grp, 1, ConfigMap) of + {_, App, VarMap} = Found -> + case lists:keyfind(K, 1, VarMap) of + {_, {EVar, F}} -> + [{App, [{EVar, F(V)}]}]; + false -> + error({not_found, {Grp, K, V}}) + end; + false -> + error({not_found, {Grp, K, V}}) + end +end. + +%% ================================================== +%% Windows INI format parser +%% ================================================== + +Compact = +fun(Gs) -> + lists:foldl( + fun({G,Vs}, Acc) -> + case orddict:find(G, Acc) of + {ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc); + error -> orddict:store(G, Vs, Acc) + end + end, orddict:new(), Gs) +end. + +Heading = +fun(L) when is_binary(L) -> + case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of + {match, [H]} -> + H; + nomatch -> + false + end +end. + +Cmd = +fun(V) -> + case os:cmd(binary_to_list(<<"echo ", V/binary>>)) of + "/bin/sh:" ++ _ -> + V; + Res -> + re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}]) + end +end. + +Var = +fun(L) -> + case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)", + [{capture,[1,2],binary}]) of + {match, [K, V]} -> + {K, Cmd(V)}; + _ -> + false + end +end. + +Collect = +fun C([H|T] = Ls, G, Acc) -> + case Var(H) of + {K, V} -> C(T, G, [{K,V}|Acc]); + false -> {{G, lists:reverse(Acc)}, Ls} + end; + C([], G, Acc) -> + {{G, lists:reverse(Acc)}, []} +end. + +Group = +fun Grp([H|T] = Ls) -> + case Heading(H) of + false -> + {G, T1} = Collect(Ls, <<>>, []), + [G | Grp(T1)]; + Head when is_binary(Head) -> + {G, T1} = Collect(T, Head, []), + [G | Grp(T1)] + end; + Grp([]) -> + [] +end. + +Parse = +fun(B) -> + Lines = + [L || + L <- re:split(B, "\\n", [{return,binary}, notempty]), + L =/= <<>>], %% may still contain a trailing <<>> + Compact(Group(Lines)) +end. + +%% ================================================== +%% End parser +%% ================================================== + +%% For actual mapping rules, see the ConfigMap and ApplyMap functions +%% at the top of this script. +Map = +fun(Groups) -> + lists:flatmap( + fun({G, Vars}) -> + [{set_env, + Compact( + lists:flatmap( + fun({K, V}) -> + ApplyMap(G, K, V) + end, Vars))}] + end, Groups) +end. + +%% This function reads the Windows INI file, parses it, then +%% transforms the parsed result into 'set_env' instructions. +case file:read_file(FILE) of + {ok, B} -> + ParseResult = Parse(B), + Map(ParseResult); + {error, _} = Error -> + Error +end. diff --git a/priv/test_config/backend.config b/priv/test_config/backend.config index 361b3da..5d455e9 100644 --- a/priv/test_config/backend.config +++ b/priv/test_config/backend.config @@ -1,14 +1,10 @@ %% -*- erlang -*- {ok, CurDir} = file:get_cwd(). +D = filename:dirname(filename:absname(SCRIPT)). +F = filename:join(D,"rvi_core/priv/config/load_ini.config"). +I = filename:join(D, "rvi_core/priv/test_config/my_ini.config"). +{ok, Instr} = file:script(F, [{'FILE', I}]). [ - {include_lib, "rvi_core/priv/config/rvi_backend.config"}, - {set_env, - [ - {rvi_core, - [{device_key, "$HOME/../../basic_backend_keys/device_key.pem"}, - {root_cert, "$HOME/../../root_keys/root_cert.crt"}, - {device_cert, "$HOME/../../basic_backend_keys/device_cert.crt"}, - {cred_dir, "$HOME/../../basic_backend_creds"} - ]} - ]} + {include_lib, "rvi_core/priv/config/rvi_backend.config"} + | Instr ]. diff --git a/priv/test_config/my_ini.config b/priv/test_config/my_ini.config new file mode 100644 index 0000000..6dd70ee --- /dev/null +++ b/priv/test_config/my_ini.config @@ -0,0 +1,6 @@ +[ system ] + +device_key = $PWD/../basic_backend_keys/device_key.pem +root_cert = $PWD/../root_keys/root_cert.crt +device_cert = $PWD/../basic_backend_keys/device_cert.crt +cred_dir = $PWD/../basic_backend_creds From 2e13f7cd847cf004f8002026dab78bbebf07aafe Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 1 Aug 2016 17:11:38 -0700 Subject: [PATCH 2/5] simplify with the use of CWD --- priv/test_config/backend.config | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/priv/test_config/backend.config b/priv/test_config/backend.config index 5d455e9..25cebef 100644 --- a/priv/test_config/backend.config +++ b/priv/test_config/backend.config @@ -1,8 +1,6 @@ %% -*- erlang -*- -{ok, CurDir} = file:get_cwd(). -D = filename:dirname(filename:absname(SCRIPT)). -F = filename:join(D,"rvi_core/priv/config/load_ini.config"). -I = filename:join(D, "rvi_core/priv/test_config/my_ini.config"). +F = filename:join(CWD,"../config/load_ini.config"). +I = filename:join(CWD, "my_ini.config"). {ok, Instr} = file:script(F, [{'FILE', I}]). [ {include_lib, "rvi_core/priv/config/rvi_backend.config"} From 788ca25fc044440f61ca89325d95b3f78f9b113f Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 9 Dec 2016 07:53:54 -0800 Subject: [PATCH 3/5] load json config + rvi_parse_ini.erl --- priv/config/load_json.config | 99 +++++++++++++++++++++++++++++++++ priv/test_config/my_json.config | 8 +++ priv/test_config/sample.config | 28 +++++----- 3 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 priv/config/load_json.config create mode 100644 priv/test_config/my_json.config diff --git a/priv/config/load_json.config b/priv/config/load_json.config new file mode 100644 index 0000000..21a3685 --- /dev/null +++ b/priv/config/load_json.config @@ -0,0 +1,99 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*- +%% Copyright (C) 2016, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ + +%% Usage: From a setup .config file: +%% {ok, Instructions} = file:script("$CWD/../config/load_json.config", +%% [{'FILE', YourJSONfile}]) +%% Then, either append or prepend Instructions to the other config +%% instructions. +%% + +%% ================================================== +%% Mapping +%% ================================================== + +Str = fun(B) -> binary_to_list(B) end. + +ConfigMap = +[{<<"system">>, rvi_core, + [ + {<<"device_key" >> , {device_key , Str}}, + {<<"root_cert" >> , {root_cert , Str}}, + {<<"device_cert">> , {device_cert , Str}}, + {<<"cred_dir" >> , {cred_dir , Str}}, + {<<"node_address">> , {node_address , Str}}, + {<<"node_service_prefix">>, {node_service_prefix, Str}}, + {<<"node_id">> , {node_id , Str}} + ]} +]. + +%% This fun converts to a 'set_env' list, or raise an error +%% +ApplyMap = +fun(Grp, K, V) -> + case lists:keyfind(Grp, 1, ConfigMap) of + {_, App, VarMap} = Found -> + case lists:keyfind(K, 1, VarMap) of + {_, {EVar, F}} -> + [{App, [{EVar, F(V)}]}]; + false -> + error({not_found, {Grp, K, V}}) + end; + false -> + error({not_found, {Grp, K, V}}) + end +end. + +%% ================================================== +%% JSON format parser +%% ================================================== + +Compact = +fun(Gs) -> + lists:foldl( + fun({G,Vs}, Acc) -> + case orddict:find(G, Acc) of + {ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc); + error -> orddict:store(G, Vs, Acc) + end + end, orddict:new(), Gs) +end. + +Parse = +fun(B) -> + JSON = jsx:decode(B, []), + Compact(JSON) +end. + +%% ================================================== +%% End parser +%% ================================================== + +%% For actual mapping rules, see the ConfigMap and ApplyMap functions +%% at the top of this script. +Map = +fun(Groups) -> + lists:flatmap( + fun({G, Vars}) -> + [{set_env, + Compact( + lists:flatmap( + fun({K, V}) -> + ApplyMap(G, K, V) + end, Vars))}] + end, Groups) +end. + +%% This function reads the Windows INI file, parses it, then +%% transforms the parsed result into 'set_env' instructions. +case file:read_file(FILE) of + {ok, B} -> + ParseResult = Parse(B), + Map(ParseResult); + {error, _} = Error -> + Error +end. diff --git a/priv/test_config/my_json.config b/priv/test_config/my_json.config new file mode 100644 index 0000000..de6ba66 --- /dev/null +++ b/priv/test_config/my_json.config @@ -0,0 +1,8 @@ +{ + "system": + {"device_key": "$HOME/../../basic_backend_keys/device_key.pem", + "root_cert": "$HOME/../../root_keys/root_cert.crt", + "device_cert": "$HOME/../../basic_backend_keys/device_cert.crt", + "cred_dir": "$HOME/../../basic_backend_creds" + } +} diff --git a/priv/test_config/sample.config b/priv/test_config/sample.config index ea84a30..4f8fa36 100644 --- a/priv/test_config/sample.config +++ b/priv/test_config/sample.config @@ -1,16 +1,18 @@ %% -*- erlang -*- -{ok, CurDir} = file:get_cwd(). +J = filename:join(CWD, "my_json.config"), +{ok, Instr} = file:script(J, [{'FILE', J}]). [ - {include_lib, "rvi_core/priv/config/rvi_sample.config"}, - {set_env, - [ - {rvi_core, - [ - {node_service_prefix, "jlr.com/vin/abc"}, - {device_key, "$HOME/../../basic_sample_keys/device_key.pem"}, - {root_cert, "$HOME/../../root_keys/root_cert.crt"}, - {device_cert, "$HOME/../../basic_sample_keys/device_cert.crt"}, - {cred_dir, "$HOME/../../basic_sample_creds"} - ]} - ]} + {include_lib, "rvi_core/priv/config/rvi_sample.config"} + | Instr + %% {set_env, + %% [ + %% {rvi_core, + %% [ + %% {node_service_prefix, "jlr.com/vin/abc"}, + %% {device_key, "$HOME/../../basic_sample_keys/device_key.pem"}, + %% {root_cert, "$HOME/../../root_keys/root_cert.crt"}, + %% {device_cert, "$HOME/../../basic_sample_keys/device_cert.crt"}, + %% {cred_dir, "$HOME/../../basic_sample_creds"} + %% ]} + %% ]} ]. From 5449894631a74acb9ae8cfa704bb5816ba33acbe Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 9 Dec 2016 07:55:03 -0800 Subject: [PATCH 4/5] load_ini.config.1 --- priv/config/load_ini.config.1 | 141 ++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 priv/config/load_ini.config.1 diff --git a/priv/config/load_ini.config.1 b/priv/config/load_ini.config.1 new file mode 100644 index 0000000..562f3b9 --- /dev/null +++ b/priv/config/load_ini.config.1 @@ -0,0 +1,141 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*- +%% Copyright (C) 2016, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +%% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ + +%% Usage: From a setup .config file: +%% {ok, Instructions} = file:script("$PRIV/config/load_ini.config", +%% [{'FILE', YourINIfile}]) +%% Then, either append or prepend Instructions to the other config +%% instructions. +%% + + +%% ================================================== +%% Windows INI format parser +%% ================================================== + +Compact = +fun(Gs) -> + lists:foldl( + fun({G,Vs}, Acc) -> + case orddict:find(G, Acc) of + {ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc); + error -> orddict:store(G, Vs, Acc) + end + end, orddict:new(), Gs) +end. + +Heading = +fun(L) when is_binary(L) -> + case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of + {match, [H]} -> + H; + nomatch -> + false + end +end. + +Cmd = +fun(V) -> + case os:cmd(binary_to_list(<<"echo ", V/binary>>)) of + "/bin/sh:" ++ _ -> + V; + Res -> + re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}]) + end +end. + +Var = +fun(L) -> + case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)", + [{capture,[1,2],binary}]) of + {match, [K, V]} -> + {K, Cmd(V)}; + _ -> + false + end +end. + +Collect = +fun C([H|T] = Ls, G, Acc) -> + case Var(H) of + {K, V} -> C(T, G, [{K,V}|Acc]); + false -> {{G, lists:reverse(Acc)}, Ls} + end; + C([], G, Acc) -> + {{G, lists:reverse(Acc)}, []} +end. + +Group = +fun Grp([H|T] = Ls) -> + case Heading(H) of + false -> + {G, T1} = Collect(Ls, <<>>, []), + [G | Grp(T1)]; + Head when is_binary(Head) -> + {G, T1} = Collect(T, Head, []), + [G | Grp(T1)] + end; + Grp([]) -> + [] +end. + +Parse = +fun(B) -> + Lines = + [L || + L <- re:split(B, "\\n", [{return,binary}, notempty]), + L =/= <<>>], %% may still contain a trailing <<>> + Compact(Group(Lines)) +end. + +%% ================================================== +%% End parser +%% ================================================== +Str = fun(B) -> binary_to_list(B) end. + +SystemGrp = +fun({K, V}) -> + case lists:member(K, [<<"device_key">>, <<"root_cert">>, + <<"device_cert">>, <<"cred_dir">>, + <<"node_address">>, <<"node_service_prefix">>, + <<"node_id">>]) of + true -> + [{binary_to_atom(K, latin1), Str(V)}]; + false -> + io:fwrite("Unknown config: ~p", [{K, V}]) + end +end. + +ServiceEdgeGrp = +fun(_) -> + [] +end. + + +Map = +fun(Groups) -> + lists:flatmap( + fun({G, Vars}) when G == <<>>; G == <<"system">> -> + [{set_env, + [{rvi_core, + lists:flatmap(SystemGrp, Vars)}]}]; + ({<<"service_edge">>, Vars}) -> + [{set_env, + [{rvi_core, + lists:map(ServiceEdgeGrp, Vars)}]}]; + (Other) -> + io:fwrite("Unknown config: ~p", [Other]), + [] + end, Groups) +end. + +case file:read_file(FILE) of + {ok, B} -> + Map(Parse(B)); + {error, _} = Error -> + Error +end. From a5fad151fe7f736ba25c55b046fb705d16309e3d Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Fri, 9 Dec 2016 07:55:47 -0800 Subject: [PATCH 5/5] rvi_parse_ini.erl --- components/rvi_common/src/rvi_parse_ini.erl | 88 +++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 components/rvi_common/src/rvi_parse_ini.erl diff --git a/components/rvi_common/src/rvi_parse_ini.erl b/components/rvi_common/src/rvi_parse_ini.erl new file mode 100644 index 0000000..874d3b3 --- /dev/null +++ b/components/rvi_common/src/rvi_parse_ini.erl @@ -0,0 +1,88 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*- +%% Copyright (C) 2014, Jaguar Land Rover +%% +%% This program is licensed under the terms and conditions of the +% Mozilla Public License, version 2.0. The full text of the +%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/ +-module(rvi_parse_ini). + +-export([file/1]). + + +file(F) -> + case file:read_file(F) of + {ok, B} -> + parse(B, F); + {error, _} = Error -> + Error + end. + +parse(B, F) -> + Lines = [L || L <- re:split(B, "\\n", [{return,binary}, notempty]), + L =/= <<>>], %% may still contain a trailing <<>> + compact(group(Lines)). + +compact(Gs) -> + lists:foldl( + fun({G,Vs}, Acc) -> + case orddict:find(G, Acc) of + {ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc); + error -> orddict:store(G, Vs, Acc) + end + end, orddict:new(), Gs). + +group([H|T] = Ls) -> + case heading(H) of + false -> + {G, T1} = collect(Ls, <<>>), + [G | group(T1)]; + Head when is_binary(Head) -> + {G, T1} = collect(T, Head), + [G | group(T1)] + end; +group([]) -> + []. + +heading(L) when is_binary(L) -> + case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of + {match, [H]} -> + H; + nomatch -> + false + end. + +collect(Ls, G) -> + collect(Ls, G, []). + +collect([H|T] = Ls, G, Acc) -> + case var(H) of + {K, V} -> collect(T, G, [{K,V}|Acc]); + false -> {{G, lists:reverse(Acc)}, Ls} + end; +collect([], G, Acc) -> + {{G, lists:reverse(Acc)}, []}. + +var(L) -> + case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)", [{capture,[1,2],binary}]) of + {match, [K, V]} -> + {K, cmd(V)}; + _ -> + false + end. + +cmd(V) -> + Env = env(), + case os:cmd(binary_to_list(<>)) of + "/bin/sh:" ++ _ -> + V; + Res -> + re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}]) + end. + +env() -> + case code:priv_dir(rvi_core) of + {error, _} -> + <<>>; + Priv -> + iolist_to_binary(["PRIV=", Priv, "; "]) + end.