Skip to content

Add user and group support #15

Closed
wants to merge 18 commits into from
+559 −100
View
31 apps/htoad/include/stdlib.hrl
@@ -15,26 +15,43 @@
}).
%%
+-record(user,
+ {
+ id,
+ name
+ }).
+
+-record(group,
+ {
+ id,
+ name
+ }).
-record(file,
{
type = file :: file | dir,
path :: undefined | string(),
- ensure = present :: present | absent,
+ user :: #user{} | string() | integer(),
+ group :: #group{} | string() | integer(),
mode,
- content = "",
- %% special flag denoting file request
- producer :: undefined | fs
+ content = ""
}).
-record(package,
{
name,
- version,
- ensure = present :: present | absent
+ version
}).
-record(shell,
{
- cmd
+ cmd,
+ run_as :: undefined | superuser | term()
+ }).
+
+-record(error_report,
+ {
+ rule :: {module(), atom()},
+ fact :: tuple(),
+ reason :: term()
}).
View
4 apps/htoad/src/htoad.app.src
@@ -25,10 +25,12 @@
htoad_lfs,
htoad_deps,
htoad_pkg,
+ htoad_pkg_yum,
htoad_pkg_brew,
htoad_pkg_apt,
htoad_io,
- htoad_shell
+ htoad_shell,
+ htoad_error
]}
]}
View
6 apps/htoad/src/htoad.erl
@@ -15,7 +15,7 @@ start(App) ->
end.
add_rules(Rules) ->
- seresye:add_rules(?ENGINE, Rules),
+ htoad_engine:add_rules(?ENGINE, Rules),
case Rules of
Module when is_atom(Module) ->
ok;
@@ -26,13 +26,13 @@ add_rules(Rules) ->
assert([{file_request, F} || F <- FReqs]).
assert(Fact) when is_list(Fact); is_tuple(Fact) ->
- seresye:assert(?ENGINE, Fact).
+ htoad_engine:assert(?ENGINE, Fact).
assert(Engine, Fact) when is_list(Fact); is_tuple(Fact) ->
seresye_engine:assert(Engine, Fact).
retract(Fact) when is_list(Fact); is_tuple(Fact) ->
- seresye:assert(?ENGINE, Fact).
+ htoad_engine:assert(?ENGINE, Fact).
retract(Engine, Fact) when is_list(Fact); is_tuple(Fact) ->
seresye_engine:retract(Engine, Fact).
View
209 apps/htoad/src/htoad_engine.erl
@@ -0,0 +1,209 @@
+%%% Extracted from:
+%%%
+%%% SERESYE, a Swarm oriented ERlang Expert SYstem Engine
+%%%
+%%% Copyright (c) 2005-2010, Francesca Gangemi, Corrado Santoro
+%%% All rights reserved.
+%%%
+%%% You may use this file under the terms of the BSD License. See the
+%%% license distributed with this project or
+%%% http://www.opensource.org/licenses/bsd-license.php
+-module(htoad_engine).
+
+%%====================================================================
+%% External exports
+%%====================================================================
+
+-export([start/0, start/1, start/2, stop/1, get_engine/1,
+ add_rules/2, add_rule/2, add_rule/3, assert/2, get_kb/1,
+ get_rules_fired/1, get_client_state/1,
+ set_client_state/2, query_kb/2, remove_rule/2, retract/2]).
+
+%% gen_server callbacks
+-export([start_link/0, start_link/1, start_link/2, init/1, handle_call/3,
+ handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%%====================================================================
+%% External functions
+%%====================================================================
+start() ->
+ seresye_sup:start_engine().
+
+start(Name) ->
+ seresye_sup:start_engine(Name).
+
+start(Name, ClientState) ->
+ seresye_sup:start_engine(Name, ClientState).
+
+set_client_state(Name, NewState) ->
+ gen_server:cast(Name, {set_client_state, NewState}).
+
+get_client_state(Name) ->
+ gen_server:call(Name, get_client_state).
+
+stop (EngineName) ->
+ (catch gen_server:call(EngineName, stop)),
+ ok.
+
+get_engine(EngineName) ->
+ gen_server:call(EngineName, get_engine).
+
+%% @doc Insert a fact in the KB.
+%% It also checks if the fact verifies any condition,
+%% if this is the case the fact is also inserted in the alpha-memory
+assert(Name, Facts) ->
+ gen_server:call(Name, {assert, Facts}, infinity).
+
+%% @doc removes a 'fact' in the Knowledge Base and if something occurs
+%% Condition is also deleted from the corresponding alpha-memory
+retract(Name, Facts) ->
+ gen_server:call(Name, {retract, Facts}, infinity).
+
+add_rules(Name, RuleList)
+ when is_list(RuleList) orelse is_atom(RuleList) ->
+ gen_server:call(Name, {add_rules, RuleList}).
+
+add_rule(Name, Fun) ->
+ gen_server:call(Name, {add_rule, Fun}).
+
+add_rule(Name, Rule, Salience) ->
+ gen_server:call(Name, {add_rule, Rule, Salience}).
+
+remove_rule(Name, Rule) ->
+ gen_server:call(Name, {remove_rule, Rule}).
+
+get_rules_fired(Name) ->
+ gen_server:call(Name, get_rules_fired).
+
+get_kb(Name) ->
+ gen_server:call(Name, get_kb).
+
+query_kb(Name, Pattern) ->
+ gen_server:call(Name, {query_kb, Pattern}).
+
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+start_link() ->
+ gen_server:start_link(?MODULE, [], []).
+
+start_link(Name) when is_atom(Name) ->
+ gen_server:start_link({local, Name}, ?MODULE, [], []).
+
+start_link(ClientState, Name) when is_atom(Name) ->
+ gen_server:start_link({local, Name}, ?MODULE, [ClientState], []).
+
+
+init([]) ->
+ {ok, seresye_engine:new()};
+init([ClientState]) ->
+ {ok, seresye_engine:new(ClientState)}.
+
+
+handle_call(get_client_state, _From, State) ->
+ Reply =
+ try
+ {ok, seresye_engine:get_client_state(State)}
+ catch
+ Type:Reason ->
+ {error, {Type, Reason}}
+ end,
+ {reply, Reply, State};
+handle_call(stop, _From, State) ->
+ {stop, normal, State};
+handle_call({assert, Facts}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:assert(State0, Facts)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call({retract, Facts}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:retract(State0, Facts)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call({add_rules, Rules}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:add_rules(State0, Rules)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call({add_rule, Rule}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:add_rule(State0, Rule)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call({add_rule, Rule, Salience}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:add_rule(State0, Rule, Salience)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call({remove_rule, Rule}, _From, State0) ->
+ {Reply, State1} =
+ try
+ {ok, seresye_engine:remove_rule(State0, Rule)}
+ catch
+ Type:Reason ->
+ {{error, {Type, Reason}}, State0}
+ end,
+ {reply, Reply, State1};
+handle_call(get_rules_fired, _From, State0) ->
+ Reply =
+ try
+ seresye_engine:get_rules_fired(State0)
+ catch
+ Type:Reason ->
+ {error, {Type, Reason}}
+ end,
+ {reply, Reply, State0};
+handle_call(get_engine, _From, State0) ->
+ {reply, State0, State0};
+handle_call(get_kb, _From, State0) ->
+ Reply =
+ try
+ seresye_engine:get_kb(State0)
+ catch
+ Type:Reason ->
+ {error, {Type, Reason}}
+ end,
+ {reply, Reply, State0};
+handle_call({query_kb, Pattern}, _From, State0) ->
+ Reply =
+ try
+ seresye_engine:query_kb(State0, Pattern)
+ catch
+ Type:Reason ->
+ {error, {Type, Reason}}
+ end,
+ {reply, Reply, State0}.
+
+handle_cast({set_client_state, CS}, State) ->
+ {noreply, seresye_engine:set_client_state(State, CS)}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
View
10 apps/htoad/src/htoad_error.erl
@@ -0,0 +1,10 @@
+-module(htoad_error).
+-include_lib("htoad/include/htoad.hrl").
+-include_lib("htoad/include/toadie.hrl").
+-include_lib("htoad/include/stdlib.hrl").
+
+-rules([report]).
+
+report(Engine, #error_report{ rule = R, fact = F, reason = Reason }) ->
+ lager:error("Rule ~p failed to process the assertion of ~p with reason: ~p", [R, F, Reason]),
+ Engine.
View
12 apps/htoad/src/htoad_host.erl
@@ -3,25 +3,23 @@
-include_lib("htoad/include/toadie.hrl").
-include_lib("htoad/include/stdlib.hrl").
--neg_rule({init, [{htoad_argument, {host, '__IGNORE_UNDERSCORE__'}}]}).
-
--rules([init, init_with_hostname_overriden, linux_ubuntu, linux_redhat]).
+-rules([init, init_with_hostname_overriden, linux_lsb, linux_redhat]).
init_with_hostname_overriden(Engine, #init{}, {htoad_argument, {host, Hostname}}) ->
initialize(Engine, Hostname).
init(Engine, #init{}) when not {rule, [{htoad_argument, {host, _}}]} ->
initialize(Engine, hostname()).
-linux_ubuntu(Engine, #file{ path="/etc/lsb-release", producer = fs, content = Content }, {operating_system_name, linux}) ->
+linux_lsb(Engine, #file{ path="/etc/lsb-release", content = Content }, {operating_system_name, linux}) ->
{match, [Dist]} = re:run(Content,".*DISTRIB_ID=(.*).*",[{capture,[1],list}]),
- lager:debug("Detected Linux ~s", [Dist]),
+ lager:debug("Linux distribution: ~s", [Dist]),
htoad:assert(Engine, {linux_distribution, Dist}).
-linux_redhat(Engine, #file{ path="/etc/redhat-release", producer = fs, content = Content }, {operating_system_name, linux}) ->
+linux_redhat(Engine, #file{ path="/etc/redhat-release", content = Content }, {operating_system_name, linux}) ->
{match, [Dist]} = re:run(Content,".*(CentOS|RedHat).*",[{capture,[1],list}]),
- lager:debug("Detected Linux ~s", [Dist]),
+ lager:debug("Linux distribution: ~s", [Dist]),
htoad:assert(Engine, {linux_distribution, Dist}).
View
151 apps/htoad/src/htoad_lfs.erl
@@ -5,36 +5,77 @@
-include_lib("kernel/include/file.hrl").
--rules([init, ensure_file_present, ensure_dir_present,
- fs_file_present, fs_file_absent, fs_dir_present, fs_dir_absent]).
+-rules([init,
+ ensure_file_present, ensure_dir_present,
+ ensure_user_access, ensure_group_access,
+ fs_file_present, fs_dir_present]).
init(Engine, #init{}) ->
lager:debug("Initialized htoad_lfs"),
Engine.
-ensure_file_present(Engine, #file{ ensure = present,
- type = file,
- producer = undefined
- } = File) ->
- filelib:ensure_dir(File#file.path),
- file:write_file(File#file.path, File#file.content),
- lager:debug("Ensured file ~s",[File#file.path]),
- chmod(File#file.path, File#file.mode),
- Engine.
+ensure_file_present(Engine, {ensure, present,
+ #file{
+ type = file
+ } = File}) ->
+
+ case file:write_file(File#file.path, File#file.content) of
+ ok ->
+ lager:debug("Ensured file ~s",[File#file.path]),
+ chmod(File#file.path, File#file.mode),
+ htoad:assert(Engine, File);
+ {error, Reason} ->
+ htoad:assert(Engine, #error_report{ rule = {?MODULE, ensure_file_present},
+ fact = {ensure, present, File},
+ reason = {error, Reason} })
+ end.
+
+
+ensure_dir_present(Engine, {ensure, present,
+ #file{
+ type = dir
+ } = Dir}) ->
+ case filelib:ensure_dir(Dir#file.path ++ "/") of
+ ok ->
+ lager:debug("Ensured directory ~s",[Dir#file.path]),
+ chmod(Dir#file.path, Dir#file.mode),
+ htoad:assert(Engine, Dir);
+ {error, Reason} ->
+ htoad:assert(Engine, #error_report{ rule = {?MODULE, ensure_dir_present},
+ fact = {ensure, present, Dir},
+ reason = {error, Reason} })
+ end.
+
+
+ensure_user_access(Engine, {ensure, present,
+ #file{
+ user = FileUser
+ } = File}, #user{}=User, #group{}) ->
+ case chown(File#file.path, FileUser, User) of
+ ok -> Engine;
+ {error, Reason} ->
+ htoad:assert(Engine, #error_report{ rule = {?MODULE, ensure_user_access},
+ fact = {ensure, present, File},
+ reason = {error, Reason} })
+ end.
+
+
+ensure_group_access(Engine, {ensure, present,
+ #file{
+ group = FileGroup
+ } = File}, #user{}, #group{}=Group) ->
+ case chgrp(File#file.path, FileGroup, Group) of
+ ok -> Engine;
+ {error, Reason} ->
+ htoad:assert(Engine, #error_report{ rule = {?MODULE, ensure_group_access},
+ fact = {ensure, present, File},
+ reason = {error, Reason} })
+ end.
-ensure_dir_present(Engine, #file{ ensure = present,
- type = dir,
- producer = undefined
- } = Dir) ->
- ok = filelib:ensure_dir(Dir#file.path ++ "/"),
- lager:debug("Ensured directory ~s",[Dir#file.path]),
- chmod(Dir#file.path, Dir#file.mode),
- Engine.
%% file system
-fs_file_present(Engine, {file_request, #file{ ensure = present,
- type = file,
- producer = fs
+fs_file_present(Engine, {file_request, #file{
+ type = file
} = File}) ->
case filelib:is_regular(File#file.path) of
true ->
@@ -44,21 +85,8 @@ fs_file_present(Engine, {file_request, #file{ ensure = present,
Engine
end.
-fs_file_absent(Engine, {file_request, #file{ ensure = absent,
- type = file,
- producer = fs
- } = File}) ->
- case filelib:is_regular(File#file.path) of
- false ->
- lager:debug("File ~s does not exist",[File#file.path]),
- htoad:assert(Engine, File);
- true ->
- Engine
- end.
-
-fs_dir_present(Engine, {file_request, #file{ ensure = present,
- type = dir,
- producer = fs
+fs_dir_present(Engine, {file_request, #file{
+ type = dir
} = File}) ->
case filelib:is_dir(File#file.path) of
true ->
@@ -68,20 +96,41 @@ fs_dir_present(Engine, {file_request, #file{ ensure = present,
Engine
end.
-fs_dir_absent(Engine, {file_request, #file{ ensure = absent,
- type = dir,
- producer = fs
- } = File}) ->
- case filelib:is_dir(File#file.path) of
- false ->
- lager:debug("Directory ~s does not exist",[File#file.path]),
- htoad:assert(Engine, File);
- true ->
- Engine
- end.
-
%% private
+
+chown(Path, FileUser, User) when is_list(FileUser), is_record(User, user) ->
+ chown(Path, #user{ id = get_uid(FileUser) }, User);
+chown(Path, FileUser, User) when is_integer(FileUser), is_record(User, user) ->
+ chown(Path, #user{ id = FileUser }, User);
+chown(_, #user{ id = Uid }, #user{ id = Uid }) ->
+ ok;
+chown(Path, #user{ id = Uid }=_FileUser, #user{ id = _ }) ->
+ lager:debug("Changed file ~s to uid ~w", [Path, Uid]),
+ file:change_owner(Path, Uid);
+chown(_, _, #user{ id = _ }) ->
+ ok.
+
+chgrp(Path, FileGroup, Group) when is_list(FileGroup), is_record(Group, group) ->
+ chgrp(Path, #group{ id = get_gid(FileGroup) }, Group);
+chgrp(Path, FileGroup, Group) when is_integer(FileGroup), is_record(Group, group) ->
+ chgrp(Path, #group{ id = FileGroup }, Group);
+chgrp(_, #group{ id = Gid }, #group{ id = Gid }) ->
+ ok;
+chgrp(Path, #group{ id = Gid }=_FileGroup, #group{ id = _ }) ->
+ lager:debug("Changed file ~s to gid ~w", [Path, Gid]),
+ file:change_group(Path, Gid);
+chgrp(_, _, #group{ id = _ }) ->
+ ok.
+
+get_uid(User) when is_list(User) ->
+ Uid = string:strip(os:cmd("id -u " ++ User), right, $\n),
+ list_to_integer(Uid).
+
+get_gid(Group) when is_list(Group) ->
+ Gid = string:strip(os:cmd("id -g " ++ Group), right, $\n),
+ list_to_integer(Gid).
+
chmod(Path, Mode) ->
case Mode of
undefined ->
@@ -92,8 +141,8 @@ chmod(Path, Mode) ->
end.
load_mode(#file{} = File) ->
- {ok, #file_info{ mode = Mode }} = file:read_file_info(File#file.path),
- File#file{ mode = Mode }.
+ {ok, #file_info{ mode = Mode, gid = Gid, uid = Uid }} = file:read_file_info(File#file.path),
+ File#file{ mode = Mode, user = #user{ id = Uid }, group = #group{ id = Gid } }.
load_content(#file{ content = dontread } = File) ->
File;
View
9 apps/htoad/src/htoad_pkg.erl
@@ -9,12 +9,12 @@
-rules([package_present]).
package_present(Engine, {package_check,
- #package{ ensure = present } = Package,
+ #package{} = Package,
"present"}) ->
lager:debug("Package ~s is present, no action needed",[format_package(Package)]),
Engine.
-ensure_package(Engine, #package{ ensure = present } = Package, Command) ->
+ensure_package(Engine, #package{} = Package, Command) ->
lager:debug("Checking if package ~s is present",[format_package(Package)]),
htoad:assert(Engine,
[
@@ -28,7 +28,10 @@ ensure_package(Engine, #package{ ensure = present } = Package, Command) ->
package_not_present(Engine, #package{} = Package, Command) ->
lager:debug("Package ~s is absent, installing",[format_package(Package)]),
- htoad:assert(Engine, Command).
+ htoad:assert(Engine, [Command, htoad_utils:on({match,
+ [{{output, Command, "installed"},
+ [],
+ [true]}]}, Package)]).
format_package(#package{ name = Name, version = undefined }) ->
Name;
View
10 apps/htoad/src/htoad_pkg_apt.erl
@@ -26,16 +26,18 @@ initialize(Engine) ->
case Package of
#package{ name = Name, version = undefined } ->
#shell{ cmd = "(apt-get -y install " ++ Name ++ " && printf installed) "
- "|| printf not_installed" };
+ "|| printf not_installed",
+ run_as = superuser };
#package{ name = Name, version = Version } ->
#shell{ cmd = "(apt-get -y install " ++ Name ++ " =" ++ Version ++ " && printf installed) "
- "|| printf not_installed" }
+ "|| printf not_installed",
+ run_as = superuser }
end).
-ensure_package(Engine, #package{ ensure = present } = Package, {package_manager, apt}) ->
+ensure_package(Engine, {ensure, present, #package{} = Package}, {package_manager, apt}) ->
htoad_pkg:ensure_package(Engine, Package, ?APT_SHELL_CHECK(Package)).
package_not_present(Engine, {package_check,
- #package{ ensure = present } = Package,
+ #package{} = Package,
"missing"}, {package_manager, apt}) ->
htoad_pkg:package_not_present(Engine, Package, ?APT_SHELL_INSTALL(Package)).
View
4 apps/htoad/src/htoad_pkg_brew.erl
@@ -34,10 +34,10 @@ init(Engine, #init{}, {operating_system_name, darwin}) ->
"|| printf not_installed" }
end).
-ensure_package(Engine, #package{ ensure = present } = Package, {package_manager, brew}) ->
+ensure_package(Engine, {ensure, present, #package{} = Package}, {package_manager, brew}) ->
htoad_pkg:ensure_package(Engine, Package, ?BREW_SHELL_CHECK(Package)).
package_not_present(Engine, {package_check,
- #package{ ensure = present } = Package,
+ #package{} = Package,
"missing"}, {package_manager, brew}) ->
htoad_pkg:package_not_present(Engine, Package, ?BREW_SHELL_INSTALL(Package)).
View
46 apps/htoad/src/htoad_pkg_yum.erl
@@ -0,0 +1,46 @@
+-module(htoad_pkg_yum).
+-include_lib("htoad/include/htoad.hrl").
+-include_lib("htoad/include/toadie.hrl").
+-include_lib("htoad/include/stdlib.hrl").
+
+-rules([init, ensure_package, package_not_present]).
+
+init(Engine, #init{}, {operating_system_name, linux}, {linux_distribution, "RedHat"}) ->
+ initialize(Engine);
+init(Engine, #init{}, {operating_system_name, linux}, {linux_distribution, "CentOS"}) ->
+ initialize(Engine).
+
+initialize(Engine) ->
+ lager:info("YUM has been selected as a package manager"),
+ htoad:assert(Engine, {package_manager, yum}).
+
+
+-define(YUM_SHELL_CHECK(Package),
+ case Package of
+ #package{ name = Name, version = undefined } ->
+ #shell{ cmd = "yum -q list installed " ++ Name ++
+ " 1>/dev/null 2>/dev/null && printf present || printf missing"};
+ #package{ name = Name, version = Version } ->
+ #shell{ cmd = "yum -q list installed " ++ Name ++ "-" ++ Version ++
+ " 1>/dev/null 2>/dev/null && printf present || printf missing"}
+ end).
+
+-define(YUM_SHELL_INSTALL(Package),
+ case Package of
+ #package{ name = Name, version = undefined } ->
+ #shell{ cmd = "yum -y install " ++ Name ++
+ " 1>/dev/null 2>/dev/null && printf installed || printf not_installed",
+ run_as = superuser };
+ #package{ name = Name, version = Version } ->
+ #shell{ cmd = "yum -y install " ++ Name ++ "-" ++ Version ++
+ " 1>/dev/null 2>/dev/null && printf installed || printf not_installed",
+ run_as = superuser }
+ end).
+
+ensure_package(Engine, {ensure, present, #package{} = Package}, {package_manager, yum}) ->
+ htoad_pkg:ensure_package(Engine, Package, ?YUM_SHELL_CHECK(Package)).
+
+package_not_present(Engine, {package_check,
+ #package{} = Package,
+ "missing"}, {package_manager, yum}) ->
+ htoad_pkg:package_not_present(Engine, Package, ?YUM_SHELL_INSTALL(Package)).
View
34 apps/htoad/src/htoad_shell.erl
@@ -3,15 +3,41 @@
-include_lib("htoad/include/toadie.hrl").
-include_lib("htoad/include/stdlib.hrl").
--rules([init, command]).
+-define(REGEX, ".*uid=([^\(]*)\\(([^\)]*).*gid=([^\\(]*)\\(([^\)]*).*").
+
+-rules([init, user, superuser, command_run_in_superuser,
+ command_run_as_superuser, command]).
init(Engine, #init{}, {operating_system_type, unix}) ->
lager:debug("Initialized htoad_shell"),
- Engine.
+ htoad:assert(Engine, #shell{ cmd = "id" }).
+
+user(Engine, {output, #shell{ cmd = "id" }, Result}) ->
+ {match, [Uid, User, Gid, Group]} = re:run(Result, ?REGEX, [{capture,[1,2,3,4],list}]),
+ lager:debug("Current user: ~s, uid: ~s, group: ~s, gid: ~s", [User, Uid, Group, Gid]),
+ htoad:assert(Engine, [#user{ id = list_to_integer(Uid), name = User },
+ #group{ id = list_to_integer(Gid), name = Group }]).
+
+superuser(Engine, #init{}, {operating_system_type, unix},
+ {user, "root"}) ->
+ htoad:assert(Engine, {?MODULE, superuser}).
+
+command_run_in_superuser(Engine, #shell{ run_as = superuser } = Shell,
+ {?MODULE, superuser}) ->
+ command(Engine, Shell).
+
+command_run_as_superuser(Engine, #shell{ cmd = Cmd, run_as = superuser } = Shell,
+ #file{ path = "/usr/bin/sudo",
+ content = dontread }) when not {rule, [{?MODULE, superuser}]} ->
+ lager:debug("Executing shell command as a super user (via sudo): ~s", [Cmd]),
+ Result = string:strip(os:cmd("/usr/bin/sudo -n " ++ Cmd), right, $\n),
+ lager:debug("Shell output for `~s` (sudo): ~s", [Cmd,Result]),
+ htoad:assert(Engine, {output, Shell, Result}).
+
-command(Engine, #shell{ cmd = Cmd } = Shell) ->
+command(Engine, #shell{ cmd = Cmd, run_as = undefined } = Shell) ->
lager:debug("Executing shell command: ~s", [Cmd]),
- Result = os:cmd(Cmd),
+ Result = string:strip(os:cmd(Cmd), right, $\n),
lager:debug("Shell output for `~s`: ~s", [Cmd, Result]),
htoad:assert(Engine, {output, Shell, Result}).
View
2 apps/htoad/src/htoad_sup.erl
@@ -22,7 +22,7 @@ start_link(Args, Files) ->
start_seresye() ->
- seresye:start(?ENGINE).
+ htoad_engine:start(?ENGINE).
%% ===================================================================
%% Supervisor callbacks
View
12 apps/htoad/src/htoad_toadie_server.erl
@@ -172,6 +172,7 @@ load_file(File) ->
"-htoad_absname(\"" ++ File ++ "\").\n"
"-import(htoad_utils, [" ++ Utils ++ "]).\n" ++ S ++ "\n \n",
{Module, Binary} = dynamic_compile:from_string(Source, [export_all,
+ nowarn_unused_function,nowarn_unused_vars,nowarn_unused_record,
return_errors, debug_info,
{parse_transform, lager_transform},
{i, filename:dirname(File)},
@@ -193,17 +194,16 @@ load_rules(File, Toadie, Bin) ->
end.
dry_run(File, Toadie) ->
- {ok, DryEngine} = seresye:start(),
+ {ok, DryEngine} = htoad_engine:start(),
lager:debug("[~w Dry run for ~s]",[DryEngine, File]),
- seresye:add_rules(DryEngine, htoad_toadies),
- seresye:assert(DryEngine, dry_run),
+ htoad_engine:add_rules(DryEngine, htoad_toadies),
+ htoad_engine:assert(DryEngine, dry_run),
case erlang:function_exported(Toadie, main, 0) of
true ->
- seresye:assert(DryEngine, Toadie:main());
+ htoad_engine:assert(DryEngine, Toadie:main());
false ->
ok
end,
lager:debug("[~w Dry run is over for ~s]",[DryEngine, File]),
- %% FIXME in seresye : seresye:stop(DryEngine).
- (catch gen_server:call(DryEngine, stop)).
+ htoad_engine:stop(DryEngine).
View
40 apps/htoad/src/htoad_transform.erl
@@ -1,6 +1,7 @@
-module(htoad_transform).
-export([parse_transform/2]).
-include_lib("htoad/include/stdlib.hrl").
+-compile({parse_transform, lager_transform}).
-record(state,{
rules = [],
@@ -12,7 +13,25 @@
absname
}).
+-record(context, {module,
+ function,
+ arity,
+ file,
+ options}).
+
+
parse_transform(Forms, Options) ->
+ #context{ file = File } = parse_trans:initial_context(Forms, Options),
+ case erl_lint:module(Forms, File, [nowarn_unused_function,nowarn_unused_vars,nowarn_unused_record]) of
+ {error, Errors, Warnings} ->
+ (length(Warnings) > 0 andalso
+ lager:debug("erl_lint warnings in ~s: ~p", [File, Warnings])),
+ throw({erl_lint, Errors});
+ {ok, Warnings} when is_list(Warnings), length(Warnings) > 0 ->
+ lager:debug("erl_lint warnings in ~s: ~p", [File, Warnings]);
+ _ ->
+ ok
+ end,
{Forms1, State} = parse_trans:transform(fun do_transform/4,
#state{ options = Options },
Forms, Options),
@@ -36,8 +55,12 @@ transform(Fun, State, Forms, Context) when is_list(Forms) ->
do_transform(attribute,{attribute, _, export, Exports} = Attr, _Context, #state{} = State) ->
{Attr, true, State#state{ exports = State#state.exports ++ Exports }};
-do_transform(attribute,{attribute, _, rules, Rules} = Attr, _Context, #state{} = State) ->
- {Attr, false, State#state{ rules = Rules }};
+do_transform(attribute,{attribute, _, rule, Rule} = Attr, _Context, #state{ rules = Rs } = State) ->
+ {Attr, false, State#state{ rules = [rule_name(Rule)|Rs] }};
+
+do_transform(attribute,{attribute, _, rules, Rules0} = Attr, _Context, #state{ rules = Rs } = State) ->
+ Rules = [ rule_name(R) || R <- Rules0 ],
+ {Attr, false, State#state{ rules = Rules ++ Rs }};
do_transform(attribute,{attribute, _, htoad_absname, AbsName} = Attr, _Context, #state{} = State) ->
{Attr, false, State#state{ absname = AbsName }};
@@ -79,13 +102,13 @@ do_transform(_Type, Form, _Context, State) ->
clause_scanner(record_expr, {record, _L, file, Fields} = Form, _Context,
#state{ file_requests = FReqs } = State) ->
case scan_file_record(Fields) of
- #file{ producer = fs, path = Path } = File when is_list(Path) ->
- {Form, true, State#state{ file_requests = [File|FReqs] }};
+ #file{ path = Path } = File when is_list(Path) ->
+ {Form, false, State#state{ file_requests = [File|FReqs] }};
_ ->
- {Form, true, State}
+ {Form, false, State}
end;
clause_scanner(_Type, Form, _Context, State) ->
- {Form, true, State}.
+ {Form, false, State}.
scan_file_record(Fields) ->
@@ -108,3 +131,8 @@ list_to_cons([],Line) ->
{nil,Line};
list_to_cons([H|T],Line) ->
{cons, Line, H, list_to_cons(T, Line)}.
+
+rule_name(A) when is_atom(A) ->
+ A;
+rule_name({A, _}) ->
+ A.
View
5 apps/htoad/src/htoad_utils.erl
@@ -1,5 +1,8 @@
-module(htoad_utils).
--export([on/2, load/1, module/1, file/1, render/2, render/3, render/4]).
+-export([ensure/2, on/2, load/1, module/1, file/1, render/2, render/3, render/4]).
+
+ensure(What, Object) ->
+ {ensure, What, Object}.
on([], Plan) ->
Plan;
View
62 doc/basic.rst
@@ -0,0 +1,62 @@
+Basic Concepts
+==============
+
+Hypnotoad's core consists of very few concepts; this is an intentional part of the design that allowed us to create a tool that is very easy to learn, extend and play with.
+
+Keep in mind, though, that usage of this tool implies at least minimal knowledge of the Erlang programming language.
+
+Fact
+----
+
+Fact is a tuple (in some cases, defined by a record) containing some logical information.
+
+Here are few examples of facts:
+
+.. code-block:: erlang
+
+ {time_of_the_day, morning}
+ #file{ path = "/etc/passwd"}
+
+Facts can be asserted & retracted:
+
+.. code-block:: erlang
+
+ htoad:assert({time_of_the_day, morning}).
+ htoad:assert([{ensure, present, #file{ path = "/tmp/hello" }},{simple_fact}]).
+ htoad:retract({time_of_the_day, morning}).
+
+
+Toadie
+------
+
+Toadie is a dynamically compiled Erlang module that is used to describe your deployment/provisioning scenarios. They normally bear
+`.htd` extension.
+
+The only difference from standard Erlang module is that it should not have the -module attribute and should define main/0 function that returns
+a fact or a list of facts to assert. No need export anything explicitly.
+
+.. code-block:: erlang
+
+ main() ->
+ [
+ on({host, "spawn.local"}, {role, build_server}),
+ ensure(present, #file{ path = "/tmp/hello" })
+ ].
+
+Toadies get automatically imported helpers from htoad_utils module (such as on/2, ensure/2, load/1, module/1, render/2-4 and so on)
+
+Rules
+-----
+
+Rule is a function that matches against a number of facts. If all facts match, the rule is executed. Most of the time
+you won't need to write your own rules; but in cases when you will, you can add them to your toadies:
+
+.. code-block:: erlang
+
+ -rules([my_rule]).
+
+ my_rule(Engine, {time_of_the_day, Time}) ->
+ io:format("It is ~p!~n",[Time]),
+ Engine.
+
+It is important for rules to return the engine. Both htoad:assert/2 and htoad:retract/2 return engines as well.
View
2 doc/index.rst
@@ -11,6 +11,8 @@ Hypnotoad is a heuristical provisioning & deployment tool.
.. toctree::
:maxdepth: 2
+ basic
+
Indices and tables
View
10 examples/simple.htd
@@ -1,13 +1,15 @@
-define(BASE_DIR, "/tmp/srv").
build_server() ->
+ File = #file{ type = dir,
+ path = filename:join([?BASE_DIR, "build_dir"])
+ },
+
[
- #file{ type = dir,
- path = filename:join([?BASE_DIR, "build_dir"])
- },
+ ensure(present, File),
on([ {operating_system_name, darwin},
{package_manager, brew} ],
- #package{ name = "git1" })
+ ensure(present, #package{ name = "git" }))
].
Something went wrong with that request. Please try again.