rex/src/suite/Unit.cpp

421 lines
13 KiB
C++

/*
Rex - A configuration management and workflow automation tool that
compiles and runs in minimal environments.
© SILO GROUP and Chris Punches, 2020.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Unit.h"
/**
* @class UnitException
* @brief Exception class for Unit related errors.
*
* This class provides an exception type for Unit related errors.
*/
class UnitException: public std::exception
{
public:
/**
* @brief Constructor for UnitException (C strings).
*
* @param message C-style string error message. The string contents are copied upon construction.
* Responsibility for deleting the char* lies with the caller.
*/
explicit UnitException(const char* message): msg_(message) {}
/**
* @brief Constructor for UnitException (C++ STL strings).
*
* @param message The error message.
*/
explicit UnitException(const std::string& message): msg_(message) {}
/**
* @brief Destructor for UnitException.
*
* Virtual to allow for subclassing.
*/
virtual ~UnitException() throw (){}
/**
* @brief Returns a pointer to the (constant) error description.
*
* @return A pointer to a const char*. The underlying memory is in posession of the Exception object.
* Callers must not attempt to free the memory.
*/
virtual const char* what() const throw () { return msg_.c_str(); }
protected:
/**
* @brief Error message.
*/
std::string msg_;
};
/**
* @class Unit
* @brief Definition of an automation task.
*
* The Unit is a definition of an automation task. Each Unit has:
* - name, used for identification and retrieval.
* - target, which is the filepath of an executable to trigger.
* - output, which is the desired output of the execution of target to STDOUT in determinations of success or failure
* (in addition to 0|non-0 exit code). If the output is set to look for "0" then it uses the exit code.
* - rectifier, which is the path to an executable in the event of a non-0 exit code or a failure to get the desired
* output.
* - required, which is used as a flag to halt or continue if rectifier does not heal the system in such a way that
* target can run successfully.
* - rectify, which is used as a flag to determine if the rectifier runs.
*
* @param LOG_LEVEL The log level to be used.
*/
Unit::Unit( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_unit_" )
{
this->LOG_LEVEL;
}
/**
* @brief Unit::load_root - Takes a JSON::Value and assigns the members to the Unit being populated.
*
* @param loader_root - The JSON::Value object to use to populate unit from. Usually supplied as the Suite's buffer
* member.
* @return - Boolean representation of success or failure.
*/
int Unit::load_root(Json::Value loader_root)
{
// TODO this needs reworked to have errmsg actually end up as a null return from json::value.get()
std::string errmsg = "SOMETHING WENT TERRIBLY WRONG IN PARSING";
// TODO this pattern is 'working but broken'. need to use a datastructure for required members and iterate
// do NOT replace this with a switch case pattern
if ( loader_root.isMember("name") )
{ this->name = loader_root.get("name", errmsg).asString(); } else
throw UnitException("No 'name' attribute specified when loading a unit.");
if ( loader_root.isMember("target") )
{ this->target = loader_root.get("target", errmsg).asString(); } else
throw UnitException("No 'target' attribute specified when loading a unit.");
if ( loader_root.isMember("is_shell_command") )
{ this->is_shell_command = loader_root.get("is_shell_command", errmsg).asBool(); } else
throw UnitException("No 'is_shell_command' attribute specified when loading a unit.");
if ( loader_root.isMember("shell_definition") )
{ this->shell_definition = loader_root.get("shell_definition", errmsg).asString(); } else
throw UnitException("No 'shell_definition' attribute specified when loading a unit.");
if ( loader_root.isMember("force_pty") )
{ this->force_pty = loader_root.get("force_pty", errmsg).asBool(); } else
throw UnitException("No 'force_pty' attribute specified when loading a unit.");
if ( loader_root.isMember("set_working_directory") )
{ this->set_working_directory = loader_root.get("set_working_directory", errmsg).asBool(); } else
throw UnitException("No 'set_working_directory' attribute specified when loading a unit.");
if ( loader_root.isMember("rectify") )
{ this->rectify = loader_root.get("rectify", errmsg).asBool(); } else
throw UnitException("No 'rectify' boolean attribute specified when loading a unit.");
if ( loader_root.isMember("rectifier") )
{ this->rectifier = loader_root.get("rectifier", errmsg).asString(); } else
throw UnitException("No 'rectifier' executable attribute specified when loading a unit.");
if ( loader_root.isMember("active") )
{ this->active = loader_root.get("active", errmsg).asBool(); } else
throw UnitException("No 'active' attribute specified when loading a unit.");
if ( loader_root.isMember("required") )
{ this->required = loader_root.get("required", errmsg).asBool(); } else
throw UnitException("No 'required' attribute specified when loading a unit.");
if ( loader_root.isMember("set_user_context") )
{ this->set_user_context = loader_root.get("set_user_context", errmsg).asBool(); } else
throw UnitException("No 'set_user_context' attribute specified when loading a unit.");
// TODO functionize this
int uid = getuid();
struct passwd * upw;
std::string errmsg_user;
// if no user field is specified then default to the currently executing user
if ( ( upw = getpwuid(uid) ) == nullptr )
{
throw UnitException( "Could not retrieve current user." );
} else {
errmsg_user = upw->pw_name;
}
// -TODO
if ( loader_root.isMember( "user" ) )
{ this->user = loader_root.get( "user", errmsg_user ).asString(); } else this->user = errmsg_user;
// TODO functionalize this
// get the current context gid as a backup value
int gid = getgid();
// declare the grp object to pull the name from once populated
struct group * grp;
// storage for backup value once retrieved
std::string errmsg_group;
// get the backup value and store it to errmsg_group
if ( ( grp = getgrgid( gid ) ) == nullptr )
{
throw UnitException("Could not retrieve current group");
} else {
errmsg_group = grp->gr_name;
}
if ( loader_root.isMember( "group" ) )
{ this->group = loader_root.get( "group", errmsg_group ).asString(); } else this->group = grp->gr_name;
if ( loader_root.isMember("supply_environment") )
{
this->supply_environment = loader_root.get("supply_environment", errmsg).asBool();
} else {
throw UnitException("No 'supply_environment' attribute specified when loading a unit.");
}
if ( loader_root.isMember("environment") )
{
this->env_vars_file = loader_root.get("environment", errmsg).asString();
} else {
throw UnitException("No 'environment' attribute specified when loading a unit.");
}
this->populated = true;
return 0;
}
/**
* @brief Retrieves the name of the unit.
*
* @return The name of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_name()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->name;
}
/**
* @brief Retrieves the target of the unit.
*
* @return The target of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_target()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->target;
}
/**
* @brief Retrieves whether the unit is a shell command.
*
* @return True if the unit is a shell command, false otherwise.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_is_shell_command()
{
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->is_shell_command;
}
/**
* @brief Retrieves the shell definition of the unit.
*
* @return The shell definition of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_shell_definition() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->shell_definition;
}
/**
* @brief Retrieves whether the unit requires a PTY.
*
* @return True if the unit requires a PTY, false otherwise.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_force_pty() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->force_pty;
}
/**
* @brief Retrieves whether the working directory should be set for the unit.
*
* @return True if the working directory should be set, false otherwise.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_set_working_directory() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->set_working_directory;
}
/**
* @brief Retrieves the working directory of the unit.
*
* @return The working directory of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_working_directory() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->working_directory;
}
/**
* @brief Retrieves the rectification status of the unit.
*
* @return The rectification status of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_rectify() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->rectify;
}
/**
* @brief Retrieves the rectifier of the unit.
*
* @return The rectifier of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_rectifier()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->rectifier;
}
/**
* @brief Retrieves the armed status of the unit.
*
* @return The armed status of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_active()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->active;
}
/**
* @brief Retrieves the requirement status of the unit.
*
* @return The requirement status of the unit.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_required()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->required;
}
/**
* @brief Retrieves whether the user context should be set for the unit.
*
* @return True if the user context should be set, false otherwise.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_set_user_context() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->set_user_context;
}
/**
* @brief Retrieves the user context for the unit.
*
* @return The string value of the user name.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_user()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->user;
}
/**
* @brief Retrieves the group context for the unit.
*
* @return The string value of the group name.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_group()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->group;
}
/**
* @brief Retrieves whether the environment should be supplied to the unit.
*
* @return True if the environment should be supplied, false otherwise.
*
* @throws UnitException if the unit has not been populated.
*/
bool Unit::get_supply_environment() {
if (!this->populated) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->supply_environment;
}
/**
* @brief Retrieves the environment file for the unit.
*
* @return The environment file for the unit.
*
* @throws UnitException if the unit has not been populated.
*/
std::string Unit::get_environment_file()
{
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
return this->env_vars_file;
}