parent
bb85754dc0
commit
7ed6e13fa5
|
@ -1,8 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(rex)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++1z -O0 -DDEBUG=1")
|
||||
set(SOURCE_FILES Rex.cpp src/loaders/abstract/loaders.cpp src/loaders/abstract/loaders.h src/json/jsoncpp.cpp src/loaders/low_level/JSON_Loader.cpp src/loaders/low_level/JSON_Loader.h src/loaders/misc/helpers.cpp src/loaders/misc/helpers.h src/loaders/abstract/Suite.cpp src/loaders/abstract/Suite.h src/loaders/abstract/Plan.cpp src/loaders/abstract/Plan.h src/loaders/abstract/Conf.cpp src/loaders/abstract/Conf.h src/loaders/abstract/Unit.cpp src/loaders/abstract/Unit.h src/loaders/abstract/Task.cpp src/loaders/abstract/Task.h src/Sproc/Sproc.cpp src/Sproc/Sproc.h src/Logger/Logger.cpp src/Logger/Logger.h)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
add_executable(rex ${SOURCE_FILES})
|
||||
add_executable(rex Rex.cpp src/json_support/jsoncpp/json.h src/json_support/jsoncpp/json-forwards.h src/json_support/jsoncpp/jsoncpp.cpp src/logger/Logger.cpp src/logger/Logger.h src/json_support/JSON.cpp src/json_support/JSON.h src/misc/helpers.cpp src/misc/helpers.h src/config/Config.cpp src/config/Config.h src/suite/Suite.cpp src/suite/Suite.h src/suite/Unit.cpp src/suite/Unit.h src/shells/shells.cpp src/shells/shells.h src/plan/Plan.cpp src/plan/Plan.h src/plan/Task.cpp src/plan/Task.h src/lcpex/liblcpex.h src/lcpex/liblcpex.cpp src/lcpex/vpty/libclpex_tty.h src/lcpex/vpty/libclpex_tty.cpp src/lcpex/Contexts.h src/lcpex/Contexts.cpp src/lcpex/helpers.h src/lcpex/string_expansion/string_expansion.h src/lcpex/string_expansion/string_expansion.cpp src/lcpex/vpty/pty_fork_mod/pty_fork.h src/lcpex/vpty/pty_fork_mod/pty_fork.cpp src/lcpex/vpty/pty_fork_mod/pty_master_open.h src/lcpex/vpty/pty_fork_mod/pty_master_open.cpp src/lcpex/vpty/pty_fork_mod/tty_functions.h src/lcpex/vpty/pty_fork_mod/tty_functions.cpp )
|
||||
|
|
106
Rex.cpp
106
Rex.cpp
|
@ -1,54 +1,61 @@
|
|||
/*
|
||||
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 <iostream>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "src/loaders/abstract/loaders.h"
|
||||
#include "src/Logger/Logger.h"
|
||||
#include "src/logger/Logger.h"
|
||||
#include "src/config/Config.h"
|
||||
#include "src/suite/Suite.h"
|
||||
#include "src/plan/Plan.h"
|
||||
|
||||
void version_info()
|
||||
{
|
||||
std::cout << "pre-release alpha" << std::endl;
|
||||
}
|
||||
|
||||
// helper function to print out commandline arguments for print_usage()
|
||||
void print_arg(const char *short_opt, const char *long_opt, const char *desc)
|
||||
{
|
||||
fprintf(stderr, " %-3s %-25s %s\n", short_opt, long_opt, desc);
|
||||
}
|
||||
|
||||
void print_section_header(const std::string &header_title)
|
||||
{
|
||||
fprintf(stderr, "\n%s:\n", header_title.c_str());
|
||||
}
|
||||
|
||||
void print_usage()
|
||||
{
|
||||
// commandline switches:
|
||||
// -h help OPTIONAL
|
||||
// -v verbose OPTIONAL
|
||||
// -c CONFIG_FILE REQUIRED
|
||||
// -p PLAN_FILE REQUIRED
|
||||
fprintf(stderr, "\nUsage:\n\trex [ -h | --help ] [ -v | --verbose ] ( ( ( -c | --config ) CONFIG_PATH ) ( -p | plan ) PLAN_PATH ) )\n");
|
||||
|
||||
fprintf( stderr, "\nUsage:\n\trex [ -h | --help ] [ -v | --verbose ] ( ( ( -c | --config ) CONFIG_PATH ) ( -p | plan ) PLAN_PATH ) )\n" );
|
||||
print_section_header("Optional Arguments");
|
||||
print_arg( "-h", "--help", "This usage screen. Mutually exclusive to all other options.");
|
||||
print_arg( "-v", "--verbose", "Sets verbose output. Generally more than you want to see.");
|
||||
print_arg( "-i", "--version_info", "Prints version information and exits. Mutually exclusive to all other options.");
|
||||
|
||||
fprintf( stderr, "\nOptional Arguments:\n");
|
||||
fprintf( stderr, "\t-h | --help\n\t\tThis usage screen. Mutually exclusive to all other options.\n");
|
||||
fprintf( stderr, "\t-v | --verbose\n\t\tSets verbose output. Generally more than you want to see.\n");
|
||||
fprintf( stderr, "\nRequired Arguments:\n");
|
||||
fprintf( stderr, "\t-c | --config\n\t\tSupply the directory path for the configuration file.\n");
|
||||
fprintf( stderr, "\t-p | --plan\n\t\tSupply the directory path for the plan file to execute.\n\n");
|
||||
print_section_header("Required Arguments");
|
||||
print_arg( "-c", "--config", "Supply the path for the configuration file.");
|
||||
print_arg( "-p", "--plan", "Supply the path for the plan file to execute.");
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main( int argc, char * argv[] )
|
||||
{
|
||||
// default verbosity setting
|
||||
|
@ -128,12 +135,18 @@ int main( int argc, char * argv[] )
|
|||
break;
|
||||
} // end switch
|
||||
} // end opts while
|
||||
|
||||
if ( version_flag ) {
|
||||
version_info();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// if the user wants the help screen, just show it and leave
|
||||
if ( (help_flag) | (! config_flag) | (! plan_flag) )
|
||||
{
|
||||
print_usage();
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// if the user supplied no config file, there's nothing to do but teach the user how to use this tool
|
||||
if (! config_flag ) {
|
||||
std::cerr << "NOT SUPPLIED: CONFIG_PATH" << std::endl;
|
||||
|
@ -149,13 +162,6 @@ int main( int argc, char * argv[] )
|
|||
interpolate( config_path );
|
||||
interpolate( plan_path );
|
||||
|
||||
// if the user wants the help screen, just show it and leave
|
||||
if ( (help_flag) | (! config_flag) | (! plan_flag) )
|
||||
{
|
||||
print_usage();
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
// default logging level
|
||||
int L_LEVEL = E_INFO;
|
||||
|
||||
|
@ -168,52 +174,38 @@ int main( int argc, char * argv[] )
|
|||
|
||||
// the main scope logger
|
||||
Logger slog = Logger( L_LEVEL, "_main_" );
|
||||
slog.log( E_INFO, "* Initialising Logging...");
|
||||
slog.log_task( E_DEBUG, "INIT", "Logging initialised." );
|
||||
|
||||
// configuration object that reads from config_path
|
||||
Conf configuration = Conf( config_path, L_LEVEL );
|
||||
slog.log_task(E_DEBUG, "INIT", "Configuration initialised.");
|
||||
|
||||
// check if context override is set in the config file
|
||||
if ( configuration.has_context_override() )
|
||||
{
|
||||
// if so, set the CWD.
|
||||
chdir( configuration.get_execution_context().c_str() );
|
||||
slog.log( E_DEBUG, "* Setting execution context: " + get_working_path() );
|
||||
}
|
||||
// load the paths to definitions of units.
|
||||
std::string unit_definitions_path = configuration.get_units_path();
|
||||
|
||||
// The Rex Paradigm:
|
||||
// - A Suite is made up of Units.
|
||||
// - A Unit is a definition of an executable along with various options surrounding its context and behaviour.
|
||||
// - A Plan is made up of Tasks.
|
||||
// - A Unit becomes a Task when it is added to a Plan.
|
||||
// initialise an empty suite (unit definitions library)
|
||||
slog.log_task( E_DEBUG, "SUITE_INIT", "Initialising Suite...");
|
||||
Suite available_definitions = Suite( L_LEVEL );
|
||||
|
||||
// load units into suite
|
||||
slog.log_task( E_INFO, "LOAD", "Loading all actionable Units into Suite..." );
|
||||
available_definitions.load_units_file( unit_definitions_path );
|
||||
|
||||
// A Plan contains what units are executed and a Suite contains the definitions of those units.
|
||||
std::string plan_file = plan_path;
|
||||
|
||||
// load the filepaths to definitions of a plan and definitions of units.
|
||||
std::string definitions_file = configuration.get_units_path();
|
||||
|
||||
// initialise an empty suite (unit definitions library)
|
||||
slog.log( E_DEBUG, "* Initialising Suite...");
|
||||
Suite available_definitions = Suite( L_LEVEL );
|
||||
|
||||
// load units into suite
|
||||
slog.log( E_INFO, "* Loading all actionable Units into Suite..." );
|
||||
available_definitions.load_units_file( definitions_file );
|
||||
|
||||
// initialise an empty plan
|
||||
slog.log( E_DEBUG, "* Initialising Plan..." );
|
||||
slog.log_task( E_DEBUG, "PLAN_INIT", "Initialising Plan..." );
|
||||
Plan plan = Plan( &configuration, L_LEVEL );
|
||||
|
||||
// load the plan the user supplied
|
||||
slog.log( E_INFO, "* Loading Plan...");
|
||||
plan.load_plan_file( plan_file );
|
||||
|
||||
|
||||
// ingest the suitable Tasks from the Suite into the Plan
|
||||
slog.log( E_INFO, "* Loading planned Tasks from Suite to Plan." );
|
||||
slog.log_task( E_INFO, "LOAD", "Loading planned Tasks from Suite to Plan." );
|
||||
plan.load_definitions( available_definitions );
|
||||
|
||||
slog.log( E_INFO, "* Ready to execute all actionable Tasks in Plan." );
|
||||
slog.log_task( E_INFO, "main", "Ready to execute all actionable Tasks in Plan." );
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Config
|
||||
|
||||
The Rex configuration is as a json object in a file that Rex is pointed to as a commandline argument.
|
||||
|
||||
In that file's json object, there is a field named "config", whose
|
||||
properties define the configuration of Rex.
|
||||
|
||||
There are currently 4 parameters that can be configured in the configuration file:
|
||||
|
||||
1. `project_root`: The root directory of the project.
|
||||
2. `units_path`: The path to the units Library.
|
||||
3. `logs_path`: The path to the logs directory.
|
||||
4. `config_version`: The version of the configuration file.
|
||||
|
||||
## Example
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"project_root": "/home/user/project",
|
||||
"units_path": "/home/user/project/units",
|
||||
"logs_path": "/home/user/project/logs",
|
||||
"config_version": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are some things to be aware of when setting the configuration file:
|
||||
|
||||
1. The configuration file must be a valid json object.
|
||||
2. The configuration file must have a field named "config", whose properties define the configuration of Rex.
|
||||
3. All values for paths in this file are relative to the `project_root` path.
|
||||
4. The `logs_path` location will be created if it does not exist when Rex begins to execute.
|
|
@ -0,0 +1,78 @@
|
|||
# Rex
|
||||
|
||||
Rex executes chains of automations.
|
||||
|
||||
Those automations are generally user-defined executables, represented in a
|
||||
project structure as json objects.
|
||||
|
||||
Rex, when executing, is supplied a config file and a plan.
|
||||
|
||||
## Config
|
||||
|
||||
The config file contains a json object that defines where the resources Rex
|
||||
will operate with are located on the filesystem, and the general project
|
||||
structure for the plan being executed.
|
||||
|
||||
## Plans
|
||||
|
||||
A `plan` is central to the operation of rex. It is a file containing a json
|
||||
object that defines which `units` to execute from the `units library`. Once
|
||||
a unit is specified in a `plan`, it is referred to as a `task`.
|
||||
|
||||
It should be noted that the config file and the plan file are not required
|
||||
to be separate files, but this is generally easier to manage from a project
|
||||
structure standpoint.
|
||||
|
||||
## Library / Units
|
||||
|
||||
The `units library` is generally where these automations are defined. This
|
||||
is a directory containing files that end with the `.units` suffix. Each of
|
||||
those files is a json object with specifications for:
|
||||
|
||||
1. What is being executed.
|
||||
2. How it is being executed.
|
||||
3. The context around which it is executed.
|
||||
4. An identifier by which the execution is referenced.
|
||||
|
||||
Exact specifications will be enumerated in a later section.
|
||||
|
||||
This allows these automations to executed with very tight control of the environment,
|
||||
current working directory, the execution process' owner and group context, whether a
|
||||
pty is used etc.
|
||||
|
||||
Among other things, the path for the units library is defined in the config file
|
||||
supplied at the command line to rex.
|
||||
|
||||
For reasons later explained, the presence of a unit definition in the units library
|
||||
does NOT mean that rex will execute that unit. It merely makes a unit available to
|
||||
be included as a task in a plan.
|
||||
|
||||
## Self-Healing Workflows
|
||||
Rex introduces self-healing workflows.
|
||||
|
||||
### Model A
|
||||
1. Write a script that does a thing.
|
||||
2. Write a script that checks if that thing is done.
|
||||
3. Set up your check script as a target in a unit.
|
||||
4. Set up your script that does the thing as that unit's rectifier.
|
||||
5. Turn on the rectify pattern in the unit definition.
|
||||
|
||||
### Model B
|
||||
Or, if you want a flow that's more simple:
|
||||
|
||||
1. Write a script that does a thing.
|
||||
2. Write a script that fixes any environmental conditions that prevent that thing from being done.
|
||||
3. Set up your target script to be the script that does the thing.
|
||||
4. Set up your rectifier script to be the script that fixes the bad condition.
|
||||
5. Turn on the rectify pattern in the unit definition.
|
||||
|
||||
## Traditional Workflows
|
||||
You don't need a dual mode of automation (though it is highly recommended). You can just do this:
|
||||
|
||||
1. Write a script that does a thing.
|
||||
2. Set the user, group to run as, well as the shell that should be used to execute within.
|
||||
3. Set the environment file to a file to be sourced for that shell containing all of your environment definitions for your automation.
|
||||
4. Turn off the rectify pattern.
|
||||
5. Repeat for every step.
|
||||
|
||||
As you can see, for small automations, it's often going to be more desirable to just write a shell script, but for very large efforts spanning many subsystems or components, you may want more control -- that's really where Rex comes in.
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,2 @@
|
|||
lcpex: setgid failed: Operation not permitted
|
||||
lcpex: Aborting: Setting GID failed: bagira/bagira
|
|
@ -0,0 +1 @@
|
|||
/usr/bin/bash: line 1: /home/phanes/development/internal/rex-rewrite/sample/environments/rex.variables: Permission denied
|
|
@ -0,0 +1 @@
|
|||
/usr/bin/bash: line 1: /home/phanes/development/internal/rex-rewrite/sample/environments/rex.variables: Permission denied
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"plan": [
|
||||
{ "name": "independent test 1", "dependencies": [ null ] }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"config": {
|
||||
"project_root": "/home/phanes/development/internal/rex-rewrite/sample",
|
||||
"units_path": "units/",
|
||||
"logs_path": "logs/",
|
||||
"shells_path": "shells/shells.definitions",
|
||||
"config_version": "5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"shells": [
|
||||
{
|
||||
"name": "bash",
|
||||
"path": "/usr/bin/bash",
|
||||
"execution_arg": "-c",
|
||||
"source_cmd": "source"
|
||||
},
|
||||
{
|
||||
"name": "ksh",
|
||||
"path": "/usr/bin/ksh",
|
||||
"execution_arg": "-c",
|
||||
"source_cmd": "source"
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "independent test 1",
|
||||
"target": "components/independent_test_1.bash --ls -s --arg",
|
||||
"is_shell_command": true,
|
||||
"shell_definition": "bash",
|
||||
"force_pty": true,
|
||||
"set_working_directory": false,
|
||||
"working_directory": "",
|
||||
"rectify": false,
|
||||
"rectifier": "echo rectifier executed",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"set_user_context": true,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"supply_environment": true,
|
||||
"environment": "environments/rex.variables"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,446 +0,0 @@
|
|||
/*
|
||||
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 "Sproc.h"
|
||||
#include "sys/stat.h"
|
||||
|
||||
#define PARENT default
|
||||
|
||||
enum PIPE_FILE_DESCRIPTORS {
|
||||
READ_END = 0,
|
||||
WRITE_END = 1
|
||||
};
|
||||
|
||||
enum READ_RESULTS {
|
||||
READ_EOF = 0,
|
||||
READ_PIPEOPEN_O_NONBLOCK = -1
|
||||
};
|
||||
|
||||
enum FORK_STATES {
|
||||
FORK_FAILURE = -1,
|
||||
CHILD = 0
|
||||
};
|
||||
|
||||
/* ------------------
|
||||
* HELPERS
|
||||
* ------------------ */
|
||||
|
||||
// converts username to UID
|
||||
// returns false on failure
|
||||
int username_to_uid( std::string username, int & uid )
|
||||
{
|
||||
// assume failure unless proven otherwise
|
||||
int r_code = false;
|
||||
|
||||
struct passwd * pw;
|
||||
if ( ( pw = getpwnam( username.c_str() ) ) != NULL )
|
||||
{
|
||||
// successful user lookup
|
||||
r_code = true;
|
||||
uid = pw->pw_uid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
};
|
||||
|
||||
// converts group name to GID
|
||||
// returns false on failure
|
||||
int groupname_to_gid( std::string groupname, int & gid )
|
||||
{
|
||||
int r_code = false;
|
||||
|
||||
struct group * gp;
|
||||
if ( ( gp = getgrnam( groupname.c_str() ) ) != NULL )
|
||||
{
|
||||
r_code = true;
|
||||
gid = gp->gr_gid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
}
|
||||
|
||||
// teebuf constructor
|
||||
teebuf::teebuf(std::streambuf *sb1, std::streambuf *sb2): sb1(sb1), sb2(sb2)
|
||||
{}
|
||||
|
||||
// teebuf overflow method
|
||||
int teebuf::overflow( int c )
|
||||
{
|
||||
if (c == EOF)
|
||||
{
|
||||
return !EOF;
|
||||
}
|
||||
else
|
||||
{
|
||||
int const r1 = sb1->sputc(c);
|
||||
int const r2 = sb2->sputc(c);
|
||||
return r1 == EOF || r2 == EOF ? EOF : c;
|
||||
}
|
||||
}
|
||||
|
||||
// teebuf sync method
|
||||
int teebuf::sync()
|
||||
{
|
||||
int const r1 = sb1->pubsync();
|
||||
int const r2 = sb2->pubsync();
|
||||
return r1 == 0 && r2 == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
// teestream impl constructor
|
||||
teestream::teestream(std::ostream &o1, std::ostream &o2) : std::ostream( &tbuf), tbuf( o1.rdbuf(), o2.rdbuf() )
|
||||
{}
|
||||
|
||||
// SET PROCESS TO A CERTAIN IDENTITY CONTEXT
|
||||
int set_identity_context( std::string task_name, std::string user_name, std::string group_name, Logger slog ) {
|
||||
// the UID and GID for the username and groupname provided for context setting
|
||||
int context_user_id;
|
||||
int context_group_id;
|
||||
|
||||
// show the user something usable in debug mode
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as user '" + user_name + "'.");
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as group_name '" + group_name + "'.");
|
||||
|
||||
// convert username to UID
|
||||
if ( username_to_uid(user_name, context_user_id ) )
|
||||
{
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] UID of '" + user_name + "' is '" + std::to_string(context_user_id ) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up UID for '" + user_name + "'.");
|
||||
return SPROC_RETURN_CODES::UID_NOT_FOUND;
|
||||
}
|
||||
|
||||
// convert group name to GID
|
||||
if ( groupname_to_gid(group_name, context_group_id ) )
|
||||
{
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] GID of '" + group_name + "' is '" + std::to_string(context_group_id ) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up GID for '" + group_name + "'.");
|
||||
return SPROC_RETURN_CODES::GID_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (setgid(context_group_id) == 0) {
|
||||
slog.log(E_DEBUG,
|
||||
"[ '" + task_name + "' ] Successfully set GID to '" + std::to_string(context_group_id) + "' (" +
|
||||
group_name + ").");
|
||||
} else {
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set GID. Panicking.");
|
||||
return SPROC_RETURN_CODES::SET_GID_FAILED;
|
||||
}
|
||||
|
||||
if (setuid(context_user_id) == 0) {
|
||||
slog.log(E_DEBUG,
|
||||
"[ '" + task_name + "' ] Successfully set UID to '" + std::to_string(context_user_id) + "' (" +
|
||||
user_name + ").");
|
||||
} else {
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set UID. Panicking.");
|
||||
return SPROC_RETURN_CODES::SET_UID_FAILED;
|
||||
}
|
||||
|
||||
return SPROC_RETURN_CODES::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Sproc::execute
|
||||
///
|
||||
/// \param input - The commandline input to execute.
|
||||
/// \return - The return code of the execution of input in the calling shell.
|
||||
int Sproc::execute(std::string shell, std::string environment_file, std::string user_name, std::string group_name, std::string command, int LOG_LEVEL, std::string task_name, bool log_to_file, std::string logs_dir )
|
||||
{
|
||||
// the logger
|
||||
Logger slog = Logger( LOG_LEVEL, "_sproc" );
|
||||
|
||||
// if you get this return value, it's an issue with this method and not your
|
||||
// called executable.
|
||||
int exit_code_raw = SPROC_RETURN_CODES::EXEC_FAILURE_GENERAL;
|
||||
|
||||
// An explanation is due here:
|
||||
// We want to log the STDOUT and STDERR of the child process, while still displaying them in the parent, in a way
|
||||
// that does not interfere with, for example libcurses compatibility.
|
||||
|
||||
// To simplify the handling of I/O, we will "tee" STDOUT and STDERR of the parent to respective log files.
|
||||
|
||||
// Then fork(), and exec() the command to execute in the child, and link its STDOUT/STDERR to the parents' in
|
||||
// realtime.
|
||||
|
||||
// Since the parent has a Tee between STDOUT/STDOUT_LOG and another between STDERR/STDERR_LOG, simply piping the
|
||||
// child STDOUT/STDERR to the parent STDOUT/STDERR should simplify I/O redirection happening here without
|
||||
// potentially corrupting user interaction with TUIs in the processes. This should give us our log and our output
|
||||
// in as hands off a way as possible with as few assumptions as possible, while still doing this in a somewhat C++-y
|
||||
// way.
|
||||
if (! is_dir( logs_dir ) ) {
|
||||
int check = mkdir( logs_dir.c_str(), 0777 );
|
||||
if (! check ) {
|
||||
slog.log( E_FATAL, "Sprocket couldn't create the logs parent directory." );
|
||||
}
|
||||
}
|
||||
|
||||
std::string timestamp = get_8601();
|
||||
|
||||
std::string contained_dir = logs_dir + "/" + task_name;
|
||||
if (! is_dir( contained_dir ) ) {
|
||||
int check = mkdir( contained_dir.c_str(), 0777 );
|
||||
if (! check ) {
|
||||
slog.log( E_FATAL, "Sprocket couldn't create the instance log directory.");
|
||||
}
|
||||
}
|
||||
// set up the "Tee" with the parent
|
||||
std::string child_stdout_log_path = contained_dir + "/" + timestamp + ".stdout.log";
|
||||
std::string child_stderr_log_path = contained_dir + "/" + timestamp + ".stderr.log";
|
||||
|
||||
std::ofstream stdout_log;
|
||||
std::ofstream stderr_log;
|
||||
|
||||
stdout_log.open( child_stdout_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||
stderr_log.open( child_stderr_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||
|
||||
// avoid cyclic dependencies between stdout and tee_out
|
||||
std::ostream tmp_stdout( std::cout.rdbuf() );
|
||||
std::ostream tmp_stderr( std::cerr.rdbuf() );
|
||||
|
||||
// writing to this ostream derivative will write to stdout log file and std::cout
|
||||
teestream tee_out(tmp_stdout, stdout_log);
|
||||
teestream tee_err(tmp_stderr, stderr_log);
|
||||
|
||||
// pop the cout/cerr buffers to the appropriate Tees' buffers
|
||||
|
||||
// These cause a segfault when used with the I/O redirection happening around fork, pipe, dup2, execl...
|
||||
//std::cout.rdbuf( tee_out.rdbuf() );
|
||||
//std::cerr.rdbuf( tee_err.rdbuf() );
|
||||
// ....and I don't know why.
|
||||
|
||||
// build the command to execute in the shell
|
||||
std::string sourcer = ". " + environment_file + " && " + command;
|
||||
|
||||
// Show the user a debug print of what is going to be executed in the shell.
|
||||
slog.log(E_INFO, "[ '" + task_name + "' ] Shell call for loading: ``" + sourcer + "``.");
|
||||
|
||||
// file descriptors for parent/child i/o
|
||||
int child_stdout_pipe[2];
|
||||
int child_stderr_pipe[2];
|
||||
|
||||
// no longer really needed, but may be handy for more verbosity settings later
|
||||
//slog.log( E_DEBUG, "[ '" + task_name + "' ] STDIN/STDOUT/STDERR file descriptors created." );
|
||||
|
||||
// man 3 pipe
|
||||
if (pipe(child_stdout_pipe) == -1 ) {
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] STDOUT PIPE FAILED");
|
||||
return SPROC_RETURN_CODES::PIPE_FAILED;
|
||||
} else {
|
||||
slog.log(E_DEBUG, "[ '" + task_name + "' ] STDOUT Piped to Log.");
|
||||
}
|
||||
|
||||
// man 3 pipe
|
||||
if (pipe(child_stderr_pipe) == -1 ) {
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] STDERR PIPE FAILED");
|
||||
return SPROC_RETURN_CODES::PIPE_FAILED;
|
||||
} else {
|
||||
slog.log(E_DEBUG, "[ '" + task_name + "' ] STDERR Piped to Log.");
|
||||
}
|
||||
|
||||
if (fcntl(child_stdout_pipe[READ_END], F_SETFD, FD_CLOEXEC) == -1) {
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fcntl(child_stdout_pipe[WRITE_END], F_SETFD, FD_CLOEXEC) == -1) {
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fcntl(child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC) == -1) {
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fcntl(child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC) == -1) {
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// fork a process
|
||||
pid_t pid = fork();
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] Process forked. Reporting. (PID: " + std::to_string(pid) + ")" );
|
||||
|
||||
switch ( pid ) {
|
||||
case FORK_STATES::FORK_FAILURE:
|
||||
{
|
||||
// fork failed
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] Fork Failed.");
|
||||
exit( FORK_FAILED );
|
||||
break;
|
||||
}
|
||||
case FORK_STATES::CHILD:
|
||||
{
|
||||
// enter child process
|
||||
slog.log(E_DEBUG, "[ '" + task_name + "' ] Entering child process.");
|
||||
|
||||
while ((dup2(child_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
|
||||
while ((dup2(child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
|
||||
|
||||
close( child_stdout_pipe[WRITE_END] );
|
||||
close( child_stdout_pipe[READ_END] );
|
||||
|
||||
close( child_stderr_pipe[WRITE_END] );
|
||||
close( child_stderr_pipe[READ_END] );
|
||||
|
||||
slog.log(E_INFO, "[ '" + task_name + "' ] TEE Logging enabled.");
|
||||
//slog.log(E_DEBUG, "[ '" + task_name + "' ] DUP2: child_*_pipe[1]->STD*_FILENO");
|
||||
|
||||
// set identity context
|
||||
// set gid and uid
|
||||
int context_status = set_identity_context(task_name, user_name, group_name, slog);
|
||||
if (!(context_status)) {
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] Identity context set failed.");
|
||||
return context_status;
|
||||
} else {
|
||||
slog.log( E_INFO, "[ '" + task_name + "' ] Identity context set as user '" + user_name + "' and group '" + group_name + "'." );
|
||||
}
|
||||
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] EXECL_CALL_PARAM: " + sourcer.c_str() );
|
||||
slog.log( E_DEBUG, "[ '" + task_name + "' ] CWD: " + get_current_dir_name() );
|
||||
|
||||
// execute our big nasty thing
|
||||
// int ret = execlp( shell.c_str(), shell.c_str(), "-c", sourcer.c_str(), (char*)NULL );
|
||||
|
||||
int ret = execl(shell.c_str(), shell.c_str(), "-c", sourcer.c_str(), (char*)NULL );
|
||||
|
||||
// print something useful to debug with if execl fails
|
||||
slog.log(E_FATAL, "[ '" + task_name + "' ] ret code: " + std::to_string(ret) + "; errno: " + strerror(errno));
|
||||
// exit child -- if this is executing, you've had a failure
|
||||
|
||||
exit(exit_code_raw);
|
||||
}
|
||||
|
||||
PARENT:
|
||||
{
|
||||
// enter the parent process
|
||||
close(child_stdout_pipe[WRITE_END]);
|
||||
close(child_stderr_pipe[WRITE_END]);
|
||||
|
||||
// buffers for reading from child fd's
|
||||
char stdout_buf[4096] = {0};
|
||||
char stderr_buf[4096] = {0};
|
||||
|
||||
// will contain a set of file descriptors to monitor representing stdout and stderr of the child process
|
||||
fd_set readfds;
|
||||
|
||||
// loop completion flags
|
||||
bool set_stdout_break = false;
|
||||
bool set_stderr_break = false;
|
||||
|
||||
// read from fd until child completes -- signaled by stdout_break and stderr_break flags
|
||||
while ((! set_stderr_break ) or (! set_stdout_break)) {
|
||||
// clear it out to make sure it's clean
|
||||
FD_ZERO( & readfds );
|
||||
|
||||
// add child stdout and stderr pipes (read end)
|
||||
FD_SET( child_stdout_pipe[READ_END], & readfds );
|
||||
FD_SET( child_stderr_pipe[READ_END], & readfds );
|
||||
|
||||
// for some reason select needs the highest number of the fd +1 of its own input
|
||||
int highest_fd = child_stderr_pipe[READ_END] > child_stdout_pipe[READ_END] ? child_stderr_pipe[READ_END] : child_stdout_pipe[READ_END];
|
||||
|
||||
// wait for one of the fd's to become readable
|
||||
if ( select( highest_fd + 1, &readfds, NULL, NULL, NULL ) >= 0 )
|
||||
{ // can read any
|
||||
if ( FD_ISSET( child_stdout_pipe[READ_END], &readfds ) )
|
||||
{ // can read child stdout pipe
|
||||
// read and return the byte size of what was read
|
||||
int stdout_count = read(child_stdout_pipe[READ_END], stdout_buf, sizeof(stdout_buf) - 1);
|
||||
|
||||
switch(stdout_count) { // switch on the count size to allow for error return handling
|
||||
case READ_PIPEOPEN_O_NONBLOCK:
|
||||
if ( errno == EINTR ) {
|
||||
continue;
|
||||
} else {
|
||||
slog.log(E_FATAL, "Pipe reading issue with child STDOUT.");
|
||||
exit( PIPE_FAILED2 );
|
||||
}
|
||||
case READ_EOF:
|
||||
// signal that STDOUT is complete
|
||||
set_stdout_break = true;
|
||||
break;
|
||||
default:
|
||||
if ( log_to_file ) {
|
||||
tee_out.write( stdout_buf, stdout_count );
|
||||
tee_out.flush();
|
||||
} else {
|
||||
std::cout.write( stdout_buf, stdout_count );
|
||||
std::cout.flush();
|
||||
}
|
||||
// clear the buffer to prevent artifacts from previous loop
|
||||
memset( &stdout_buf[0], 0, sizeof( stdout_buf ) -1 );
|
||||
}
|
||||
}
|
||||
if ( FD_ISSET( child_stderr_pipe[READ_END], & readfds ) ) {
|
||||
// can read child stderr pipe
|
||||
// so do so
|
||||
|
||||
// read and return the byte size of what was read
|
||||
int stderr_count = read( child_stderr_pipe[READ_END], stderr_buf, sizeof( stderr_buf ) -1 );
|
||||
|
||||
// switch on the count size to allow for error return handling
|
||||
switch( stderr_count ) {
|
||||
case READ_RESULTS::READ_PIPEOPEN_O_NONBLOCK:
|
||||
if ( errno == EINTR ) {
|
||||
continue;
|
||||
} else {
|
||||
perror( "read stderr" );
|
||||
slog.log( E_FATAL, "Pipe reading issue with child STDERR." );
|
||||
exit( PIPE_FAILED3 );
|
||||
}
|
||||
case READ_RESULTS::READ_EOF:
|
||||
// let the loop know the STDERR criteria has been met
|
||||
set_stderr_break = true;
|
||||
// continue the loop
|
||||
continue;
|
||||
default:
|
||||
// write the buffer contents to the STDERR Tee
|
||||
tee_err.write( stderr_buf, stderr_count );
|
||||
// flush the TEE
|
||||
tee_err.flush();
|
||||
// clear the buffer to prevent artifacts from previous loop
|
||||
memset( &stderr_buf[0], 0, sizeof( stderr_buf ) -1 );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// select error, fatal, throw
|
||||
slog.log( E_FATAL, "Fatal error, Unknown.");
|
||||
} // end select/if
|
||||
}
|
||||
|
||||
close( child_stderr_pipe[READ_END] );
|
||||
close( child_stdout_pipe[READ_END] );
|
||||
|
||||
// clean up Tee
|
||||
stdout_log.close();
|
||||
stderr_log.close();
|
||||
|
||||
// wait for the child to exit
|
||||
while ( ( pid = waitpid(pid, &exit_code_raw, 0 ) ) == -1 ) {}
|
||||
|
||||
}
|
||||
}
|
||||
return WEXITSTATUS( exit_code_raw );
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_SPROCKET_H
|
||||
#define REX_SPROCKET_H
|
||||
|
||||
#include "../Logger/Logger.h"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include "errno.h"
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <streambuf>
|
||||
#include "unistd.h"
|
||||
#include <sys/wait.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <fstream>
|
||||
#include "fcntl.h"
|
||||
#include "../loaders/misc/helpers.h"
|
||||
|
||||
|
||||
// exit codes for Rex
|
||||
enum SPROC_RETURN_CODES {
|
||||
SUCCESS = true,
|
||||
UID_NOT_FOUND = -404,
|
||||
GID_NOT_FOUND = -405,
|
||||
SET_GID_FAILED = -401,
|
||||
SET_UID_FAILED = -404,
|
||||
EXEC_FAILURE_GENERAL = -666,
|
||||
PIPE_FAILED3 = -996,
|
||||
PIPE_FAILED2 = -997,
|
||||
PIPE_FAILED = -998,
|
||||
DUP2_FAILED = -999,
|
||||
FORK_FAILED = -1000
|
||||
};
|
||||
|
||||
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
||||
// should be able to receive path of binary to be executed as well as any parameters
|
||||
class Sproc {
|
||||
public:
|
||||
// call the object. return value is enum representing external execution attempt not binary exit code
|
||||
static int execute(
|
||||
std::string shell,
|
||||
std::string environment_file,
|
||||
std::string user_name,
|
||||
std::string group_name,
|
||||
std::string command,
|
||||
int LOG_LEVEL,
|
||||
std::string task_name,
|
||||
bool log_to_file,
|
||||
std::string logs_dir
|
||||
);
|
||||
};
|
||||
|
||||
// a teebuff implementation
|
||||
class teebuf: public std::streambuf
|
||||
{
|
||||
public:
|
||||
// Construct a streambuf which tees output to both input
|
||||
// streambufs.
|
||||
teebuf(std::streambuf * sb1, std::streambuf * sb2);
|
||||
private:
|
||||
virtual int overflow(int c);
|
||||
virtual int sync();
|
||||
std::streambuf * sb1;
|
||||
std::streambuf * sb2;
|
||||
};
|
||||
|
||||
// a simple helper class to create a tee stream from two input streams
|
||||
class teestream : public std::ostream
|
||||
{
|
||||
public:
|
||||
// Construct an ostream which tees output to the supplied ostreams.
|
||||
teestream( std::ostream & o1, std::ostream & o2);
|
||||
teebuf tbuf;
|
||||
};
|
||||
|
||||
#endif //REX_SPROCKET_H
|
|
@ -0,0 +1,346 @@
|
|||
#include "Config.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief General exception class for the Conf class.
|
||||
*
|
||||
* This exception class can be thrown to indicate an error in the Conf class. It provides an error message describing the problem.
|
||||
*/
|
||||
class ConfigLoadException: public std::exception
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor that takes a C-style string error message.
|
||||
*
|
||||
* The string contents are copied upon construction. The caller is responsible for deleting the char*.
|
||||
*
|
||||
* @param message C-style string error message.
|
||||
*/
|
||||
explicit ConfigLoadException(const char* message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructor that takes a C++ STL string error message.
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit ConfigLoadException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor to allow for subclassing.
|
||||
*/
|
||||
virtual ~ConfigLoadException() throw (){}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the 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_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set the string value of a key
|
||||
*
|
||||
* This method sets the value of a key as a string in a member variable.
|
||||
* It first retrieves the string value of the key using the `get_string` method.
|
||||
* If the key is not found, a `ConfigLoadException` is thrown.
|
||||
* If the key is found, the string value is interpolated using the `interpolate` method and then assigned to the member variable.
|
||||
* The method logs the task in the log file using the `slog.log_task` method.
|
||||
*
|
||||
* @param keyname The name of the key to retrieve
|
||||
* @param object_member The reference to the member variable to store the value
|
||||
* @param filename The name of the configuration file
|
||||
*
|
||||
* @throws ConfigLoadException If the key is not found in the configuration file
|
||||
*/
|
||||
void Conf::set_object_s(std::string keyname, std::string & object_member, std::string filename )
|
||||
{
|
||||
std::string jval_s;
|
||||
if ( this->get_string(jval_s, keyname) != 0) {
|
||||
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
||||
} else {
|
||||
interpolate(jval_s);
|
||||
object_member = jval_s;
|
||||
}
|
||||
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set the string value of a key with a derived path
|
||||
*
|
||||
* This method sets the value of a key as a string in a member variable with a derived path.
|
||||
* It first retrieves the string value of the key using the `get_string` method.
|
||||
* If the key is not found, a `ConfigLoadException` is thrown.
|
||||
* If the key is found, the string value is interpolated using the `interpolate` method and then passed to the `prepend_project_root` method to derive the path.
|
||||
* The derived path is then assigned to the member variable.
|
||||
* The method logs the task in the log file using the `slog.log_task` method.
|
||||
*
|
||||
* @param keyname The name of the key to retrieve
|
||||
* @param object_member The reference to the member variable to store the value
|
||||
* @param filename The name of the configuration file
|
||||
*
|
||||
* @throws ConfigLoadException If the key is not found in the configuration file
|
||||
*/
|
||||
void Conf::set_object_s_derivedpath(std::string keyname, std::string & object_member, std::string filename )
|
||||
{
|
||||
std::string jval_s;
|
||||
if ( this->get_string(jval_s, keyname) != 0) {
|
||||
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
|
||||
} else {
|
||||
interpolate(jval_s);
|
||||
object_member = prepend_project_root( jval_s );
|
||||
}
|
||||
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set the boolean value of a key
|
||||
*
|
||||
* This method sets the value of a key as a boolean in a member variable.
|
||||
* It first retrieves the boolean value of the key using the `get_bool` method.
|
||||
* If the key is not found, a `ConfigLoadException` is thrown.
|
||||
* If the key is found, the boolean value is assigned to the member variable.
|
||||
* The method logs the task in the log file using the `slog.log_task` method.
|
||||
*
|
||||
* @param keyname The name of the key to retrieve
|
||||
* @param object_member The reference to the member variable to store the value
|
||||
* @param filename The name of the configuration file
|
||||
*
|
||||
* @throws ConfigLoadException If the key is not found in the configuration file
|
||||
*/
|
||||
void Conf::set_object_b(std::string keyname, bool & object_member, std::string filename )
|
||||
{
|
||||
bool jval_b;
|
||||
if ( this->get_bool(jval_b, keyname) != 0) {
|
||||
throw ConfigLoadException( "'" + keyname + "' boolean is not set in the config file supplied: " + filename );
|
||||
} else {
|
||||
object_member = jval_b;
|
||||
}
|
||||
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "' " + std::to_string(object_member));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prepend the project root to a relative path
|
||||
*
|
||||
* This method prepends the project root to a given relative path.
|
||||
* The project root is stored as a member variable in the `Conf` object.
|
||||
* The method concatenates the project root and the relative path separated by a forward slash (/) and returns the result.
|
||||
*
|
||||
* @param relative_path The relative path to prepend the project root to
|
||||
*
|
||||
* @return The concatenated path with the project root and the relative path
|
||||
*/
|
||||
std::string Conf::prepend_project_root( std::string relative_path)
|
||||
{
|
||||
return this->project_root + "/" + relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the absolute path from a relative path
|
||||
*
|
||||
* This function takes a relative path and returns the corresponding absolute path.
|
||||
* The absolute path is obtained by calling the `realpath` function.
|
||||
* If the `realpath` function returns a null pointer, an error message is printed to the standard error stream and an empty string is returned.
|
||||
*
|
||||
* @param relative_path The relative path to be converted to an absolute path
|
||||
*
|
||||
* @return The absolute path corresponding to the relative path
|
||||
*/
|
||||
std::string get_absolute_path(const std::string &relative_path)
|
||||
{
|
||||
char resolved_path[1024];
|
||||
memset(resolved_path, 0, sizeof(resolved_path));
|
||||
|
||||
if( realpath( relative_path.c_str(), resolved_path) == nullptr ) {
|
||||
std::cerr << "Error resolving path: " << relative_path << std::endl;
|
||||
return "";
|
||||
}
|
||||
|
||||
return std::string(resolved_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a path exists
|
||||
*
|
||||
* This method checks if a given path exists.
|
||||
* If the path exists, a log entry is made in the log file using the `slog.log_task` method with log level `E_DEBUG`.
|
||||
* If the path does not exist, a log entry is made in the log file with log level `E_FATAL` and a `ConfigLoadException` is thrown.
|
||||
*
|
||||
* @param keyname The name of the key to be used in the log entry
|
||||
* @param path The path to be checked
|
||||
*
|
||||
* @throws ConfigLoadException If the path does not exist
|
||||
*/
|
||||
void Conf::checkPathExists( std::string keyname, const std::string &path ) {
|
||||
if ( exists( path ) ) {
|
||||
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "'" + keyname + "' exists ('" + path + "')" );
|
||||
} else {
|
||||
this->slog.log_task( E_FATAL, "SANITY_CHECKS", "'" + keyname + "' does not exist ('" + path + "')" );
|
||||
throw ConfigLoadException("Path does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load the shells
|
||||
*
|
||||
* This method loads the shell definitions from a file.
|
||||
* The path to the shell definition file is stored as a member variable in the `Conf` object.
|
||||
* The method first logs the task of loading the shells using the `slog.log_task` method.
|
||||
* The method then tries to load the JSON file using the `load_json_file` method.
|
||||
* If an exception occurs while loading the file, a log entry with log level `E_FATAL` is made and a `ConfigLoadException` is thrown.
|
||||
* The method then retrieves the serialized value of the "shells" key using the `get_serialized` method.
|
||||
* If the "shells" key is not found, a log entry with log level `E_FATAL` is made and a `ConfigLoadException` is thrown.
|
||||
* The method then loops through the serialized values, loads each shell definition using the `load_root` method of the `Shell` class, and stores each shell in the `shells` vector.
|
||||
* The method logs each loaded shell using the `slog.log_task` method.
|
||||
*
|
||||
* @throws ConfigLoadException If there is an error parsing the shell definition file
|
||||
*/
|
||||
void Conf::load_shells() {
|
||||
this->slog.log_task( E_DEBUG, "SHELLS", "Loading shells..." );
|
||||
try {
|
||||
// load the test file.
|
||||
this->load_json_file( this->shell_definitions_path );
|
||||
} catch (std::exception& e) {
|
||||
this->slog.log_task( E_FATAL, "SHELLS", "Unable to load shell definition file: '" + this->shell_definitions_path + "'. Error: " + e.what());
|
||||
throw ConfigLoadException("Parsing error in shell definitions file.");
|
||||
}
|
||||
Json::Value jbuff;
|
||||
|
||||
if ( this-> get_serialized( jbuff, "shells" ) != 0 ) {
|
||||
this->slog.log_task( E_FATAL, "SHELLS", "Parsing error: '" + this->shell_definitions_path + "'. Error: 'shells' key not found." );
|
||||
throw ConfigLoadException("Parsing error in shell definitions file.");
|
||||
}
|
||||
|
||||
Shell tmp_S = Shell( this->LOG_LEVEL );
|
||||
for ( int index = 0; index < jbuff.size(); index++ )
|
||||
{
|
||||
tmp_S.load_root( jbuff[index] );
|
||||
this->shells.push_back( tmp_S );
|
||||
this->slog.log_task( E_DEBUG, "SHELLS", "Loaded shell: '" + tmp_S.name + "'" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a shell by name
|
||||
*
|
||||
* This method retrieves a `Shell` object by its name from the `shells` vector.
|
||||
* The method loops through the `shells` vector and compares each shell's name to the input name.
|
||||
* If a match is found, the matching shell is returned.
|
||||
* If no match is found, a `ConfigLoadException` is thrown.
|
||||
*
|
||||
* @param name The name of the shell to retrieve
|
||||
*
|
||||
* @return The `Shell` object corresponding to the input name
|
||||
*
|
||||
* @throws ConfigLoadException If the input name does not match any shell in the `shells` vector
|
||||
*/
|
||||
Shell Conf::get_shell_by_name( std::string name ) {
|
||||
for (auto &shell: this->shells) {
|
||||
if (shell.name == name) {
|
||||
return shell;
|
||||
}
|
||||
}
|
||||
throw ConfigLoadException("The shell specified ('" + name + "') is not defined in the shell definitions file.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @class Conf
|
||||
* @brief Loads the configuration for the application
|
||||
*
|
||||
* This class is responsible for loading the configuration for the application. It uses the `JSON_Loader` class to
|
||||
* parse the JSON configuration file and stores the relevant information.
|
||||
*
|
||||
* @note Currently, the class only supports loading a single configuration file. However, it can be expanded to
|
||||
* support loading multiple files or detecting when a directory path is supplied for the units_path or plan_path and
|
||||
* importing all Tasks and Units.
|
||||
*
|
||||
* @param filename - The filename to load the configuration from.
|
||||
* @param LOG_LEVEL - The log level to use for logging messages.
|
||||
*/
|
||||
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
this->slog.log_task( E_DEBUG, "LOAD", "Loading configuration file: " + filename );
|
||||
|
||||
interpolate( filename );
|
||||
|
||||
try {
|
||||
// load the test file.
|
||||
this->load_json_file( filename );
|
||||
} catch (std::exception& e) {
|
||||
this->slog.log( E_FATAL, "Unable to load configuration file: '" + filename + "'. Error: " + e.what());
|
||||
throw ConfigLoadException("Parsing error in configuration file.");
|
||||
}
|
||||
Json::Value jbuff;
|
||||
|
||||
if ( this->get_serialized( jbuff, "config" ) != 0) {
|
||||
this->slog.log_task( E_FATAL, "LOAD", "Unable to locate 'config' object in configuration file: " + filename );
|
||||
throw ConfigLoadException("Unable to locate 'config' object in configuration file.");
|
||||
} else {
|
||||
this->slog.log_task( E_DEBUG, "LOAD", "Found 'config' object in configuration file: " + filename );
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
set_object_s( "project_root", this->project_root, filename );
|
||||
// convert to an absolute path after all the interpolation is done.
|
||||
this->project_root = get_absolute_path( this->project_root );
|
||||
|
||||
// all other paths are relative to project_root
|
||||
set_object_s_derivedpath( "units_path", this->units_path, filename );
|
||||
set_object_s_derivedpath( "logs_path", this->logs_path, filename );
|
||||
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename );
|
||||
|
||||
// ensure these paths exists, with exception to the logs_path, which will be created at runtime
|
||||
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "Checking for sanity..." );
|
||||
checkPathExists( "project_root", this->project_root );
|
||||
checkPathExists( "units_path", this->units_path );
|
||||
checkPathExists( "shells_path", this->shell_definitions_path );
|
||||
|
||||
// shells are scoped beyond plan so they need to be considered part of config
|
||||
load_shells();
|
||||
|
||||
this->slog.log_task( E_DEBUG, "LOAD", "CONFIGURATION LOADED." );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the path to the Unit definition file
|
||||
*
|
||||
* This function returns the path to the Unit definition file that was specified in the configuration file.
|
||||
*
|
||||
* @return The path to the Unit definition file.
|
||||
*/
|
||||
std::string Conf::get_units_path() { return this->units_path; }
|
||||
|
||||
/**
|
||||
* @brief Gets the path to the logs directory
|
||||
*
|
||||
* This function returns the path to the logs directory that was specified in the configuration file.
|
||||
*
|
||||
* @return The path to the logs directory.
|
||||
*/
|
||||
std::string Conf::get_logs_path() { return this->logs_path; }
|
||||
|
||||
/**
|
||||
* @brief Gets the project root directory
|
||||
*
|
||||
* This function returns the path to the project root directory that was specified in the configuration file.
|
||||
*
|
||||
* @return The path to the project root directory.
|
||||
*/
|
||||
std::string Conf::get_project_root() { return this->project_root; }
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
Rex - A configuration management and workflow automation tool that
|
||||
compiles and runs in minimal environments.
|
||||
|
||||
© SILO GROUP LLC, 2023.
|
||||
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_CONF_H
|
||||
#define REX_CONF_H
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include "../json_support/JSON.h"
|
||||
#include "../logger/Logger.h"
|
||||
#include "../misc/helpers.h"
|
||||
#include "../shells/shells.h"
|
||||
|
||||
#define STRINGIZE2(s) #s
|
||||
#define STRINGIZE(s) STRINGIZE2(s)
|
||||
#define IMPL_CONFIG_VERSION 6
|
||||
#define VERSION_STRING STRINGIZE(IMPL_CONFIG_VERSION)
|
||||
|
||||
/**
|
||||
* @class Conf
|
||||
* @brief This class loads configuration from a JSON file and provides access to the configuration values.
|
||||
*
|
||||
* The class `Conf` extends the `JSON_Loader` class and implements a constructor that takes the filename of the configuration file and the LOG_LEVEL to be used by the internal `Logger` instance.
|
||||
* The class provides getter methods to access the `units_path`, `logs_path`, and `project_root` values.
|
||||
*/
|
||||
class Conf: public JSON_Loader {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Conf object and loads the configuration from the specified file
|
||||
*
|
||||
* @param filename The name of the configuration file
|
||||
* @param LOG_LEVEL The log level to be used by the internal Logger instance
|
||||
*/
|
||||
Conf(std::string filename, int LOG_LEVEL);
|
||||
|
||||
/**
|
||||
* @brief Returns the path to the units directory
|
||||
*
|
||||
* @return The path to the units directory
|
||||
*/
|
||||
std::string get_units_path();
|
||||
|
||||
/**
|
||||
* @brief Returns the path to the logs directory
|
||||
*
|
||||
* @return The path to the logs directory
|
||||
*/
|
||||
std::string get_logs_path();
|
||||
|
||||
/**
|
||||
* @brief Returns the root directory of the project
|
||||
*
|
||||
* @return The root directory of the project
|
||||
*/
|
||||
std::string get_project_root();
|
||||
|
||||
/**
|
||||
* @brief Returns the Shell object with the specified name
|
||||
*
|
||||
* @param name The name of the Shell object
|
||||
*
|
||||
* @return The Shell object with the specified name
|
||||
*/
|
||||
Shell get_shell_by_name(std::string name);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The path to the units directory
|
||||
*/
|
||||
std::string units_path;
|
||||
|
||||
/**
|
||||
* @brief The path to the logs directory
|
||||
*/
|
||||
std::string logs_path;
|
||||
|
||||
/**
|
||||
* @brief The root directory of the project
|
||||
*/
|
||||
std::string project_root;
|
||||
|
||||
/**
|
||||
* @brief The path to the shell definitions
|
||||
*/
|
||||
std::string shell_definitions_path;
|
||||
|
||||
/**
|
||||
* @brief The vector of Shell objects
|
||||
*/
|
||||
std::vector<Shell> shells;
|
||||
|
||||
/**
|
||||
* @brief Checks if the specified path exists
|
||||
*
|
||||
* @param keyname The name of the key in the JSON file
|
||||
* @param path The path to be checked
|
||||
*/
|
||||
void checkPathExists(std::string keyname, const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief The log level to be used by the internal Logger instance
|
||||
*/
|
||||
int LOG_LEVEL;
|
||||
|
||||
/**
|
||||
* @brief The internal Logger instance
|
||||
*/
|
||||
Logger slog;
|
||||
|
||||
/**
|
||||
* @brief Prepends the project root to a relative path
|
||||
*
|
||||
* @param relative_path The relative path to be modified
|
||||
*
|
||||
* @return The modified path with the project root prepended
|
||||
*/
|
||||
std::string prepend_project_root(std::string relative_path);
|
||||
|
||||
/**
|
||||
* @brief Sets a string object member from a JSON file
|
||||
*
|
||||
* @param keyname The name of the key in the JSON file
|
||||
* @param object_member The string object member to be set
|
||||
* @param filename The name of the JSON file
|
||||
*/
|
||||
void set_object_s(std::string keyname, std::string &object_member, std::string filename);
|
||||
|
||||
/**
|
||||
* @brief Sets a string object member from a JSON file, with the path derived from the project root
|
||||
*
|
||||
* @param keyname The name of the key in the JSON file
|
||||
* @param object_member The string object member to be set
|
||||
* @param filename The name of the JSON file
|
||||
*/
|
||||
void set_object_s_derivedpath(std::string keyname, std::string &object_member, std::string filename);
|
||||
|
||||
/**
|
||||
* @brief Sets a boolean object member from a JSON file
|
||||
*
|
||||
* @param keyname The name of the key in the JSON file
|
||||
* @param object_member The boolean object member to be set
|
||||
* @param filename The name of the JSON file
|
||||
*/
|
||||
void set_object_b(std::string keyname, bool &object_member, std::string filename);
|
||||
|
||||
/**
|
||||
* @brief Loads the shell definitions from the specified file
|
||||
*/
|
||||
void load_shells();
|
||||
};
|
||||
|
||||
#endif //REX_CONF_H
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
Rex - A configuration management and workflow automation tool that
|
||||
compiles and runs in minimal environments.
|
||||
|
||||
© SILO GROUP LLC, 2023.
|
||||
|
||||
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 "JSON.h"
|
||||
|
||||
|
||||
/**
|
||||
* @class JSON_Loader_NotReady
|
||||
* @brief Exception thrown when a member function is called before data is populated.
|
||||
*
|
||||
* This class is derived from the standard library class `std::runtime_error` and is thrown
|
||||
* when a member function is called before the JSON data has been populated.
|
||||
*/
|
||||
class JSON_Loader_NotReady : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the JSON_Loader_NotReady exception.
|
||||
*
|
||||
* This constructor creates an instance of the JSON_Loader_NotReady exception and sets its error message.
|
||||
*/
|
||||
JSON_Loader_NotReady() : std::runtime_error("JSON_Loader: Tried to access JSON without actually populating JSON.") {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class JSON_Loader_FileNotFound
|
||||
* @brief Exception thrown when the requested file could not be found.
|
||||
*
|
||||
* This class is derived from the standard library class `std::runtime_error` and is thrown
|
||||
* when the file specified in the JSON_Loader constructor could not be found.
|
||||
*/
|
||||
class JSON_Loader_FileNotFound : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the JSON_Loader_FileNotFound exception.
|
||||
*
|
||||
* This constructor creates an instance of the JSON_Loader_FileNotFound exception and sets its error message.
|
||||
*/
|
||||
JSON_Loader_FileNotFound() : std::runtime_error("JSON_Loader: The requested file could not be found.") {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class JSON_Loader_InvalidJSON
|
||||
* @brief Exception thrown when the provided JSON could not be parsed.
|
||||
*
|
||||
* This class is derived from the standard library class `std::runtime_error` and is thrown
|
||||
* when the JSON data provided to the JSON_Loader could not be parsed.
|
||||
*/
|
||||
class JSON_Loader_InvalidJSON : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the JSON_Loader_InvalidJSON exception.
|
||||
*
|
||||
* This constructor creates an instance of the JSON_Loader_InvalidJSON exception and sets its error message.
|
||||
*/
|
||||
JSON_Loader_InvalidJSON() : std::runtime_error("JSON_Loader: The JSON provided could not be parsed.") {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class JSON_Loader
|
||||
* @brief The base class for loading and parsing JSON data.
|
||||
*
|
||||
* The JSON_Loader class provides the functionalities shared between its derived classes, Suite and Plan.
|
||||
* It is responsible for initializing the JSON data to an unpopulated state.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Constructor for the JSON_Loader base class.
|
||||
*
|
||||
* This constructor initializes the JSON data to an unpopulated state and sets the logging level.
|
||||
*
|
||||
* @param LOG_LEVEL The logging level for the JSON_Loader instance.
|
||||
*/
|
||||
JSON_Loader::JSON_Loader(int LOG_LEVEL) : slog(LOG_LEVEL, "_json_")
|
||||
{
|
||||
this->populated = false;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads a JSON-formatted string into the JSON_Loader instance.
|
||||
*
|
||||
* This function takes a JSON-formatted string as input, parses it using a Json::Reader, and stores the result in the protected member `json_root`.
|
||||
* If the parsing is successful, the `populated` flag is set to `true`.
|
||||
*
|
||||
* @param input The JSON-formatted string to be loaded into the JSON_Loader instance.
|
||||
* @throws JSON_Loader_InvalidJSON if the input string could not be parsed.
|
||||
*/
|
||||
void JSON_Loader::load_json_string(std::string input)
|
||||
{
|
||||
Json::Reader json_reader;
|
||||
|
||||
std::ifstream json_file_ifstream(input.c_str(), std::ifstream::binary);
|
||||
|
||||
bool parsingSuccessful = json_reader.parse(json_file_ifstream, this->json_root);
|
||||
|
||||
if (!parsingSuccessful)
|
||||
{
|
||||
this->slog.log(E_FATAL, "Failed to parse adhoc JSON value: " + json_reader.getFormattedErrorMessages());
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->slog.log(E_DEBUG, "Successfully parsed JSON string with " + std::to_string(this->json_root.size()) + " elements. Value: '" + input + "'.");
|
||||
}
|
||||
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads a JSON-formatted file into the JSON_Loader instance.
|
||||
*
|
||||
* This function takes a file path as input, parses the file using a Json::Reader, and stores the result in the protected member `json_root`.
|
||||
* If the parsing is successful, the `populated` flag is set to `true`.
|
||||
*
|
||||
* @param filename The file path to the JSON-formatted file to be loaded into the JSON_Loader instance.
|
||||
* @throws JSON_Loader_FileNotFound if the file specified in `filename` could not be found.
|
||||
* @throws JSON_Loader_InvalidJSON if the contents of the file could not be parsed.
|
||||
*/
|
||||
void JSON_Loader::load_json_file(std::string filename)
|
||||
{
|
||||
Json::Reader json_reader;
|
||||
|
||||
if (!exists(filename))
|
||||
{
|
||||
this->slog.log_task(E_FATAL, "LOAD", "File '" + filename + "' does not exist.");
|
||||
throw JSON_Loader_FileNotFound();
|
||||
}
|
||||
|
||||
std::ifstream json_file_ifstream(filename, std::ifstream::binary);
|
||||
|
||||
bool parsingSuccessful = json_reader.parse(json_file_ifstream, this->json_root);
|
||||
|
||||
if (!parsingSuccessful)
|
||||
{
|
||||
this->slog.log_task(E_FATAL, "PARSING", "Failed to parse file '" + filename + "': " + json_reader.getFormattedErrorMessages());
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->slog.log_task(E_DEBUG, "PARSING", "Parsed '" + filename + "' with " + std::to_string(this->json_root.size()) + " element(s).");
|
||||
}
|
||||
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the string representation of the `json_root` member.
|
||||
*
|
||||
* This function returns the string representation of the `json_root` member, which contains the parsed JSON data.
|
||||
*
|
||||
* @return The string representation of the `json_root` member.
|
||||
* @throws JSON_Loader_NotReady if the JSON data has not yet been populated.
|
||||
*/
|
||||
std::string JSON_Loader::as_string()
|
||||
{
|
||||
if (!this->populated)
|
||||
{
|
||||
throw JSON_Loader_NotReady();
|
||||
}
|
||||
|
||||
return this->json_root.asString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the serialized representation of a specified key to a passed Json::Value reference.
|
||||
*
|
||||
* This function takes a reference to a Json::Value object as input, along with a string representing the key to search for.
|
||||
* If the key is found in the `json_root` member, its value is assigned to the passed Json::Value reference.
|
||||
*
|
||||
* @param input A reference to the Json::Value object to receive the value of the specified key.
|
||||
* @param key The key name to search for in the `json_root` member.
|
||||
* @return 0 if the key was found and its value was assigned to the passed Json::Value reference, 1 if the key was not found.
|
||||
* @throws JSON_Loader_NotReady if the JSON data has not yet been populated.
|
||||
*/
|
||||
int JSON_Loader::get_serialized(Json::Value &input, std::string key)
|
||||
{
|
||||
if (!this->populated)
|
||||
{
|
||||
throw JSON_Loader_NotReady();
|
||||
}
|
||||
|
||||
if (this->json_root.isMember(key))
|
||||
{
|
||||
input = this->json_root[key];
|
||||
return 0;
|
||||
}
|
||||
|
||||
this->slog.log_task(E_FATAL, "PARSING", "Failed to find key '" + key + "'.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the string representation of a specified key to a passed string reference.
|
||||
*
|
||||
* This function takes a reference to a string object as input, along with a string representing the key to search for.
|
||||
* If the key is found in the `json_root` member, its string representation is assigned to the passed string reference.
|
||||
*
|
||||
* @param input A reference to the string object to receive the string representation of the specified key.
|
||||
* @param key The key name to search for in the `json_root` member.
|
||||
* @return 0 if the key was found and its string representation was assigned to the passed string reference, 1 if the key was not found.
|
||||
* @throws JSON_Loader_NotReady if the JSON data has not yet been populated.
|
||||
*/
|
||||
int JSON_Loader::get_string(std::string &input, std::string key)
|
||||
{
|
||||
if (!this->populated)
|
||||
{
|
||||
throw JSON_Loader_NotReady();
|
||||
}
|
||||
|
||||
if (this->json_root.isMember(key))
|
||||
{
|
||||
input = this->json_root[key].asString();
|
||||
return 0;
|
||||
}
|
||||
|
||||
this->slog.log_task(E_FATAL, "PARSING", "Failed to find key '" + key + "'.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the boolean representation of a specified key to a passed bool reference.
|
||||
*
|
||||
* This function takes a reference to a bool object as input, along with a string representing the key to search for.
|
||||
* If the key is found in the `json_root` member, its boolean representation is assigned to the passed bool reference.
|
||||
*
|
||||
* @param input A reference to the bool object to receive the boolean representation of the specified key.
|
||||
* @param key The key name to search for in the `json_root` member.
|
||||
* @return 0 if the key was found and its boolean representation was assigned to the passed bool reference, 1 if the key was not found.
|
||||
* @throws JSON_Loader_NotReady if the JSON data has not yet been populated.
|
||||
*/
|
||||
int JSON_Loader::get_bool(bool &input, std::string key)
|
||||
{
|
||||
if (!this->populated)
|
||||
{
|
||||
throw JSON_Loader_NotReady();
|
||||
}
|
||||
|
||||
if (this->json_root.isMember(key))
|
||||
{
|
||||
input = this->json_root[key].asBool();
|
||||
return 0;
|
||||
}
|
||||
|
||||
this->slog.log_task(E_FATAL, "PARSING", "Failed to find key '" + key + "'.");
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
#ifndef REX_JSON_H
|
||||
#define REX_JSON_H
|
||||
|
||||
#include "jsoncpp/json.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include "../misc/helpers.h"
|
||||
#include "../logger/Logger.h"
|
||||
|
||||
/**
|
||||
* @class JSON_Loader
|
||||
* @brief Loads and parses JSON data
|
||||
*
|
||||
* This class is responsible for loading and parsing JSON data from a file or a string. It uses the `JsonCpp` library to
|
||||
* handle the parsing and provides functions to access the data in a safe and convenient way.
|
||||
*
|
||||
* @param LOG_LEVEL - The log level to use for logging messages.
|
||||
*/
|
||||
class JSON_Loader {
|
||||
protected:
|
||||
/// The root of the JSON data
|
||||
Json::Value json_root;
|
||||
/// Indicates whether the JSON data has been successfully loaded and parsed
|
||||
bool populated;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the JSON_Loader class
|
||||
*
|
||||
* This constructor initializes the JSON_Loader object and sets the log level to use for logging messages.
|
||||
*
|
||||
* @param LOG_LEVEL - The log level to use for logging messages.
|
||||
*/
|
||||
JSON_Loader(int LOG_LEVEL);
|
||||
|
||||
/**
|
||||
* @brief Loads JSON data from a file
|
||||
*
|
||||
* This function loads JSON data from a file and parses it using the `JsonCpp` library.
|
||||
*
|
||||
* @param filename - The path to the file to load the JSON data from.
|
||||
*/
|
||||
void load_json_file(std::string filename);
|
||||
|
||||
/**
|
||||
* @brief Loads JSON data from a string
|
||||
*
|
||||
* This function parses JSON data from a string using the `JsonCpp` library.
|
||||
*
|
||||
* @param input - The string containing the JSON data.
|
||||
*/
|
||||
void load_json_string(std::string input);
|
||||
|
||||
/**
|
||||
* @brief Returns the JSON data as a string
|
||||
*
|
||||
* This function returns the JSON data in string form.
|
||||
*
|
||||
* @return The JSON data as a string.
|
||||
*/
|
||||
std::string as_string();
|
||||
|
||||
/**
|
||||
* @brief Gets the value of a key in the JSON data
|
||||
*
|
||||
* This function retrieves the value of a key in the JSON data and returns it in a `Json::Value` object. If the
|
||||
* key does not exist, an error is logged and a default `Json::Value` object is returned.
|
||||
*
|
||||
* @param input - A reference to a `Json::Value` object to store the result.
|
||||
* @param key - The key to retrieve the value for.
|
||||
*
|
||||
* @return 0 if the key was found and its value was retrieved successfully, 1 otherwise.
|
||||
*/
|
||||
int get_serialized(Json::Value &input, std::string key);
|
||||
|
||||
/**
|
||||
* @brief Gets the string value of a key in the JSON data
|
||||
*
|
||||
* This function retrieves the string value of a key in the JSON data and returns it in a `std::string` object.
|
||||
* If the key does not exist or its value is not a string, an error is logged and a default `std::string` object
|
||||
* is returned.
|
||||
*
|
||||
* @param input - A reference to a `std::string` object to store the result.
|
||||
* @param key - The key to retrieve the string value for.
|
||||
*
|
||||
* @return 0 if the key was found and its string value was retrieved successfully, 1 otherwise.
|
||||
*/
|
||||
int get_string(std::string &input, std::string key);
|
||||
|
||||
/**
|
||||
* @brief Gets the boolean value of a key in the JSON data
|
||||
*
|
||||
* This function retrieves the boolean value of a key in the JSON data and returns it in a `bool` object. If the
|
||||
* key does not exist or its value is not a boolean, an error is logged and a default `bool` object is returned.
|
||||
*
|
||||
* @param input - A reference to a `bool` object to store the result.
|
||||
* @param key - The key to retrieve the boolean value for.
|
||||
*
|
||||
* @return 0 if the key was found and its boolean value was retrieved successfully, 1 otherwise.
|
||||
*/
|
||||
int get_bool(bool &input, std::string key);
|
||||
|
||||
private:
|
||||
/// The logger object used for logging messages
|
||||
Logger slog;
|
||||
/// The log level to use for logging messages
|
||||
int LOG_LEVEL;
|
||||
};
|
||||
|
||||
#endif //REX_JSON_H
|
|
@ -0,0 +1,70 @@
|
|||
#include "Contexts.h"
|
||||
|
||||
|
||||
// converts username to UID
|
||||
int username_to_uid( std::string username, int & uid )
|
||||
{
|
||||
// assume failure unless proven otherwise
|
||||
int r_code = false;
|
||||
|
||||
struct passwd * pw;
|
||||
if ( ( pw = getpwnam( username.c_str() ) ) != NULL )
|
||||
{
|
||||
// successful user lookup
|
||||
r_code = true;
|
||||
uid = pw->pw_uid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
};
|
||||
|
||||
// converts group name to GID
|
||||
int groupname_to_gid( std::string groupname, int & gid )
|
||||
{
|
||||
int r_code = false;
|
||||
|
||||
struct group * gp;
|
||||
if ( ( gp = getgrnam( groupname.c_str() ) ) != NULL )
|
||||
{
|
||||
r_code = true;
|
||||
gid = gp->gr_gid;
|
||||
} else {
|
||||
// failed lookup, do nothing, assumed failure
|
||||
}
|
||||
return r_code;
|
||||
}
|
||||
|
||||
// SET PROCESS TO A CERTAIN IDENTITY CONTEXT
|
||||
int set_identity_context( std::string user_name, std::string group_name ) {
|
||||
// the UID and GID for the username and groupname provided for context setting
|
||||
int context_user_id;
|
||||
int context_group_id;
|
||||
|
||||
int res = 0;
|
||||
|
||||
// convert username to UID
|
||||
if (! ( res = username_to_uid(user_name, context_user_id ) ) )
|
||||
{
|
||||
return ERROR_NO_SUCH_USER;
|
||||
}
|
||||
|
||||
// convert group name to GID
|
||||
if (! ( res = groupname_to_gid(group_name, context_group_id ) ) )
|
||||
{
|
||||
return ERROR_NO_SUCH_GROUP;
|
||||
}
|
||||
|
||||
if ( ( res = setgid(context_group_id) ) ) {
|
||||
perror("lcpex: setgid failed");
|
||||
return ERROR_SETGID_FAILED;
|
||||
}
|
||||
|
||||
if ( ( res = setuid(context_user_id) ) ) {
|
||||
perror("lcpex: setuid failed");
|
||||
return ERROR_SETUID_FAILED;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef LCPEX_CONTEXTS_H
|
||||
#define LCPEX_CONTEXTS_H
|
||||
|
||||
#include <string>
|
||||
#include <csignal>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <iostream>
|
||||
|
||||
enum IDENTITY_CONTEXT_ERRORS {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_NO_SUCH_USER,
|
||||
ERROR_NO_SUCH_GROUP,
|
||||
ERROR_NO_SUCH_USER_IN_GROUP,
|
||||
ERROR_SETGID_FAILED,
|
||||
ERROR_SETUID_FAILED,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Converts a username to a user ID (UID)
|
||||
*
|
||||
* @param username The username to convert
|
||||
* @param uid A reference to the resulting UID
|
||||
*
|
||||
* @return A boolean indicating whether the conversion was successful
|
||||
*
|
||||
* This function takes a username as a string and returns the corresponding UID in the `uid` parameter.
|
||||
* The function returns a boolean indicating whether the conversion was successful.
|
||||
* If the username is not found, the function returns false and the value of `uid` is unchanged.
|
||||
*/
|
||||
int username_to_uid( std::string username, int & uid );
|
||||
|
||||
|
||||
/**
|
||||
* @brief Converts a group name to a group ID (GID)
|
||||
*
|
||||
* @param groupname The group name to convert
|
||||
* @param gid A reference to the resulting GID
|
||||
*
|
||||
* @return A boolean indicating whether the conversion was successful
|
||||
*
|
||||
* This function takes a group name as a string and returns the corresponding GID in the `gid` parameter.
|
||||
* The function returns a boolean indicating whether the conversion was successful.
|
||||
* If the group name is not found, the function returns false and the value of `gid` is unchanged.
|
||||
*/
|
||||
int groupname_to_gid( std::string groupname, int & gid );
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the execution context for a process
|
||||
*
|
||||
* @param user_name The username to use for the execution context
|
||||
* @param group_name The group name to use for the execution context
|
||||
*
|
||||
* @return An error code indicating the result of the context setting
|
||||
*
|
||||
* This function takes a username and group name as input and sets the execution context for the process.
|
||||
* The function converts the username and group name to UID and GID respectively, and sets the process's
|
||||
* user and group identity using the setuid() and setgid() functions.
|
||||
*
|
||||
* If either the username or group name is not found, the function returns `ERROR_NO_SUCH_USER` or `ERROR_NO_SUCH_GROUP` respectively.
|
||||
* If the call to setgid() fails, the function returns `ERROR_SETGID_FAILED`.
|
||||
* If the call to setuid() fails, the function returns `ERROR_SETUID_FAILED`.
|
||||
* If the context setting is successful, the function returns `ERROR_NONE`.
|
||||
*/
|
||||
int set_identity_context( std::string user_name, std::string group_name );
|
||||
|
||||
|
||||
#endif //LCPEX_CONTEXTS_H
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef LCPEX_HELPERS_H
|
||||
#define LCPEX_HELPERS_H
|
||||
|
||||
// helper for sanity
|
||||
enum PIPE_ENDS {
|
||||
READ_END = 0,
|
||||
WRITE_END = 1
|
||||
};
|
||||
|
||||
// helper for sanity
|
||||
// these do NOT represent fd values
|
||||
enum CHILD_PIPE_NAMES {
|
||||
STDOUT_READ = 0,
|
||||
STDERR_READ = 1
|
||||
};
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
|
||||
|
||||
#endif //LCPEX_HELPERS_H
|
|
@ -0,0 +1,338 @@
|
|||
#include "liblcpex.h"
|
||||
|
||||
std::string prefix_generator(
|
||||
std::string command,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
) {
|
||||
std::string prefix = "";
|
||||
if ( is_shell_command ) {
|
||||
|
||||
// it's a shell command, so we need to set up a shell execution of the command
|
||||
prefix = shell_path;
|
||||
|
||||
// if the shell takes an argument to execute a command, add it enclosed in quotes
|
||||
if (shell_execution_arg != "") {
|
||||
// add the execution arg
|
||||
prefix += " " + shell_execution_arg + " ";
|
||||
} else {
|
||||
// no execution arg, so add a space
|
||||
prefix += " ";
|
||||
}
|
||||
|
||||
if (supply_environment) {
|
||||
// add the source subcommand
|
||||
prefix += "'" + shell_source_subcommand + " " + environment_file_path + " && " + command + "'";
|
||||
} else {
|
||||
// no environment supplied, so just add the command
|
||||
prefix += "'" + command + "'";
|
||||
}
|
||||
} else {
|
||||
// it's not a shell command, so we can just execute it directly
|
||||
prefix = command;
|
||||
}
|
||||
std::cout << "prefix: " << prefix << std::endl;
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
int lcpex(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool force_pty,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
) {
|
||||
|
||||
// generate the prefix
|
||||
std::string prefix = prefix_generator(
|
||||
command,
|
||||
is_shell_command,
|
||||
shell_path,
|
||||
shell_execution_arg,
|
||||
supply_environment,
|
||||
shell_source_subcommand,
|
||||
environment_file_path
|
||||
);
|
||||
command = prefix;
|
||||
|
||||
// if we are forcing a pty, then we will use the vpty library
|
||||
if( force_pty )
|
||||
{
|
||||
return exec_pty( command, stdout_log_file, stderr_log_file, context_override, context_user, context_group, supply_environment );
|
||||
}
|
||||
|
||||
// otherwise, we will use the execute function
|
||||
return execute( command, stdout_log_file, stderr_log_file, context_override, context_user, context_group, supply_environment );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the close-on-exec flag of a file descriptor
|
||||
*
|
||||
* This function uses the `fcntl` function to set the close-on-exec flag
|
||||
* of the specified file descriptor `fd`. If the `fcntl` call fails,
|
||||
* the function will print an error message to `stderr` and exit with status 1.
|
||||
*
|
||||
* @param fd The file descriptor for which to set the close-on-exec flag
|
||||
*/
|
||||
void set_cloexec_flag(int fd)
|
||||
{
|
||||
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
|
||||
{
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function is executed by the child process to run a shell command
|
||||
*
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param processed_command The command to be executed, after processing
|
||||
* @param fd_child_stdout_pipe The file descriptor for the child process's standard output pipe
|
||||
* @param fd_child_stderr_pipe The file descriptor for the child process's standard error pipe
|
||||
*
|
||||
* The run_child_process() function takes the parameters context_override, context_user, context_group, processed_command,
|
||||
* fd_child_stdout_pipe and fd_child_stderr_pipe.
|
||||
*
|
||||
* If context_override is set to true, the child process will run under the specified context_user and context_group.
|
||||
* If either the context_user or context_group does not exist, an error message will be displayed and the process will exit.
|
||||
*
|
||||
* The function first redirects the child process's standard output and standard error to pipes.
|
||||
*
|
||||
* If context_override is set to true, the child process sets its identity context using the set_identity_context() function.
|
||||
* If an error occurs while setting the identity context, a message will be displayed and the process will exit.
|
||||
*
|
||||
* Finally, the child process calls execvp() with the processed_command to run the shell command.
|
||||
* If the execvp() function fails, an error message is displayed.
|
||||
*/
|
||||
void run_child_process(bool context_override, const char* context_user, const char* context_group, char* processed_command[], int fd_child_stdout_pipe[], int fd_child_stderr_pipe[]) {
|
||||
while ((dup2(fd_child_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
|
||||
while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
|
||||
|
||||
if ( context_override ) {
|
||||
int context_result = set_identity_context(context_user, context_group);
|
||||
switch (context_result) {
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int exit_code = execvp(processed_command[0], processed_command);
|
||||
perror("failed on execvp in child");
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
|
||||
int execute(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
){
|
||||
// this does three things:
|
||||
// - execute a dang string as a subprocess command
|
||||
// - capture child stdout/stderr to respective log files
|
||||
// - TEE child stdout/stderr to parent stdout/stderr
|
||||
|
||||
// turn our command string into something execvp can consume
|
||||
char ** processed_command = expand_env(command );
|
||||
|
||||
// if the user chose to supply the environment, then we need to clear the environment
|
||||
// before we fork the process, so that the child process will inherit the environment
|
||||
// from the parent process
|
||||
if ( environment_supplied ) {
|
||||
clearenv();
|
||||
}
|
||||
|
||||
// open file handles to the two log files we need to create for each execution
|
||||
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
|
||||
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
|
||||
|
||||
// create the pipes for the child process to write and read from using its stdin/stdout/stderr
|
||||
int fd_child_stdout_pipe[2];
|
||||
int fd_child_stderr_pipe[2];
|
||||
|
||||
if ( pipe(fd_child_stdout_pipe ) == -1 ) {
|
||||
perror( "child stdout pipe" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||
perror( "child stderr pipe" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// using O_CLOEXEC to ensure that the child process closes the file descriptors
|
||||
set_cloexec_flag( fd_child_stdout_pipe[READ_END] );
|
||||
set_cloexec_flag( fd_child_stderr_pipe[READ_END] );
|
||||
set_cloexec_flag( fd_child_stdout_pipe[WRITE_END] );
|
||||
set_cloexec_flag( fd_child_stderr_pipe[WRITE_END] );
|
||||
|
||||
// status result basket for the parent process to capture the child's exit status
|
||||
int status = 616;
|
||||
pid_t pid = fork();
|
||||
|
||||
switch( pid ) {
|
||||
case -1:
|
||||
{
|
||||
// fork failed
|
||||
perror("fork failure");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
case 0:
|
||||
{
|
||||
// child process
|
||||
run_child_process(
|
||||
context_override,
|
||||
context_user.c_str(),
|
||||
context_group.c_str(),
|
||||
processed_command,
|
||||
fd_child_stdout_pipe,
|
||||
fd_child_stderr_pipe
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// parent process
|
||||
|
||||
// The parent process has no need to access the entrance to the pipe, so fd_child_*_pipe[1|0] should be closed
|
||||
// within that process too:
|
||||
close(fd_child_stdout_pipe[WRITE_END]);
|
||||
close(fd_child_stderr_pipe[WRITE_END]);
|
||||
|
||||
// attempt to write to stdout,stderr from child as well as to write each to file
|
||||
char buf[BUFFER_SIZE];
|
||||
|
||||
// contains the byte count of the last read from the pipe
|
||||
ssize_t byte_count;
|
||||
|
||||
// watched_fds for poll() to wait on
|
||||
struct pollfd watched_fds[2];
|
||||
|
||||
// populate the watched_fds array
|
||||
|
||||
// child STDOUT to parent
|
||||
watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].fd = fd_child_stdout_pipe[READ_END];
|
||||
watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events = POLLIN;
|
||||
|
||||
// child STDERR to parent
|
||||
watched_fds[CHILD_PIPE_NAMES::STDERR_READ].fd = fd_child_stderr_pipe[READ_END];
|
||||
watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events = POLLIN;
|
||||
|
||||
// number of files poll() reports as ready
|
||||
int num_files_readable;
|
||||
|
||||
// loop flag
|
||||
bool break_out = false;
|
||||
|
||||
// loop until we've read all the data from the child process
|
||||
while ( ! break_out ) {
|
||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||
if (num_files_readable == -1) {
|
||||
// error occurred in poll()
|
||||
perror("poll");
|
||||
exit(1);
|
||||
}
|
||||
if (num_files_readable == 0) {
|
||||
// poll reports no files readable
|
||||
break_out = true;
|
||||
break;
|
||||
}
|
||||
for (int this_fd = 0; this_fd < 2; this_fd++) {
|
||||
if (watched_fds[this_fd].revents & POLLIN) {
|
||||
// this pipe is readable
|
||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||
|
||||
if (byte_count == -1) {
|
||||
// error reading from pipe
|
||||
perror("read");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (byte_count == 0) {
|
||||
// reached EOF on one of the streams but not a HUP
|
||||
// we've read all we can this cycle, so go to the next fd in the for loop
|
||||
continue;
|
||||
} else {
|
||||
// byte count was sane
|
||||
// write to stdout,stderr
|
||||
if (this_fd == CHILD_PIPE_NAMES::STDOUT_READ) {
|
||||
// the child's stdout pipe is readable
|
||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write(STDOUT_FILENO, buf, byte_count);
|
||||
} else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) {
|
||||
// the child's stderr pipe is readable
|
||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write(STDERR_FILENO, buf, byte_count);
|
||||
} else {
|
||||
// this should never happen
|
||||
perror("Logic error!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLERR) {
|
||||
close(watched_fds[this_fd].fd);
|
||||
break_out = true;
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||
// this pipe has hung up
|
||||
close(watched_fds[this_fd].fd);
|
||||
break_out = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// close the log file handles
|
||||
fclose(stdout_log_fh);
|
||||
fclose(stderr_log_fh);
|
||||
if WIFEXITED(status) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -617;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
#ifndef LCPEX_LIBLCPEX_H
|
||||
#define LCPEX_LIBLCPEX_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <iostream>
|
||||
#include "string_expansion/string_expansion.h"
|
||||
#include "helpers.h"
|
||||
#include "Contexts.h"
|
||||
#include "vpty/pty_fork_mod/tty_functions.h"
|
||||
#include "vpty/pty_fork_mod/pty_fork.h"
|
||||
#include "vpty/libclpex_tty.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief This function is executed by the child process to run a shell command
|
||||
*
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param processed_command The command to be executed, after processing
|
||||
* @param fd_child_stdout_pipe The file descriptor for the child process's standard output pipe
|
||||
* @param fd_child_stderr_pipe The file descriptor for the child process's standard error pipe
|
||||
*
|
||||
* If context_override is set to true, the child process will run under the specified context_user and context_group.
|
||||
* If either the context_user or context_group does not exist, an error message will be displayed and the process will exit.
|
||||
*
|
||||
* The function first redirects the child process's standard output and standard error to pipes.
|
||||
*
|
||||
* If context_override is set to true, the child process sets its identity context using the set_identity_context() function.
|
||||
* If an error occurs while setting the identity context, a message will be displayed and the process will exit.
|
||||
*
|
||||
* Finally, the child process calls execvp() with the processed_command to run the shell command.
|
||||
* If the execvp() function fails, an error message is displayed.
|
||||
*/
|
||||
int execute(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Executes a command with logging and optional context switching
|
||||
*
|
||||
* @param command The command to be executed
|
||||
* @param stdout_log_file The file to log the standard output of the command
|
||||
* @param stderr_log_file The file to log the standard error of the command
|
||||
* @param context_override Indicates whether to override the current execution context
|
||||
* @param context_user The user to switch to for execution context
|
||||
* @param context_group The group to switch to for execution context
|
||||
* @param force_pty Indicates whether to force a pseudoterminal (pty) for the command execution
|
||||
* @param is_shell_command Indicates whether the command is a shell command
|
||||
* @param shell_path The path to the shell executable
|
||||
* @param shell_execution_arg The argument used to execute a command in the shell
|
||||
* @param supply_environment Indicates whether to supply an environment
|
||||
* @param shell_source_subcommand The shell subcommand used to source the environment file
|
||||
* @param environment_file_path The path to the environment file
|
||||
*
|
||||
* @return The exit status of the executed command
|
||||
*
|
||||
* This function executes a command with logging and optional context switching. The function generates
|
||||
* a prefix for the command using the `prefix_generator` function, which sets up a shell execution if
|
||||
* the command is a shell command. If a pseudoterminal (pty) is forced, the `exec_pty` function is used
|
||||
* to execute the command, otherwise the `execute` function is used. The standard output and standard
|
||||
* error of the command are logged to the provided log files. If context overriding is requested, the
|
||||
* execution context is switched to the provided user and group.
|
||||
*/
|
||||
int lcpex(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool force_pty,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Generates a command prefix for execution
|
||||
*
|
||||
* @param command The main command to be executed
|
||||
* @param is_shell_command Indicates whether the command is a shell command
|
||||
* @param shell_path The path to the shell executable
|
||||
* @param shell_execution_arg The argument used to execute a command in the shell
|
||||
* @param supply_environment Indicates whether to supply an environment
|
||||
* @param shell_source_subcommand The shell subcommand used to source the environment file
|
||||
* @param environment_file_path The path to the environment file
|
||||
*
|
||||
* @return The generated command prefix
|
||||
*
|
||||
* This function generates a prefix for a command to be executed, based on the provided parameters.
|
||||
* If the command is a shell command, the prefix will be set up for a shell execution. If the shell
|
||||
* takes an argument to execute a command, it will be added to the prefix. If an environment is to be
|
||||
* supplied, the shell subcommand for sourcing the environment file and the environment file path will
|
||||
* be added to the prefix. If the command is not a shell command, the command will be returned as is.
|
||||
*/
|
||||
std::string prefix_generator(
|
||||
std::string command,
|
||||
bool is_shell_command,
|
||||
std::string shell_path,
|
||||
std::string shell_execution_arg,
|
||||
bool supply_environment,
|
||||
std::string shell_source_subcommand,
|
||||
std::string environment_file_path
|
||||
);
|
||||
|
||||
#endif //LCPEX_LIBLCPEX_H
|
|
@ -0,0 +1,24 @@
|
|||
#include "string_expansion.h"
|
||||
|
||||
// convert a string to a char** representing our artificial argv to be consumed by execvp
|
||||
char ** expand_env(const std::string& var, int flags )
|
||||
{
|
||||
std::vector<std::string> vars;
|
||||
|
||||
wordexp_t p;
|
||||
if(!wordexp(var.c_str(), &p, flags))
|
||||
{
|
||||
if(p.we_wordc)
|
||||
for(char** exp = p.we_wordv; *exp; ++exp)
|
||||
vars.push_back(exp[0]);
|
||||
wordfree(&p);
|
||||
}
|
||||
|
||||
char ** arr = new char*[vars.size() + 1];
|
||||
for(size_t i = 0; i < vars.size(); i++){
|
||||
arr[i] = new char[vars[i].size() + 1];
|
||||
strcpy(arr[i], vars[i].c_str());
|
||||
}
|
||||
arr[vars.size()] = (char * ) nullptr;
|
||||
return arr;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef LCPEX_STRING_EXPANSION_H
|
||||
#define LCPEX_STRING_EXPANSION_H
|
||||
|
||||
#include <wordexp.h>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* @brief Convert a string to a 'char**' representing an argument list
|
||||
*
|
||||
* This function takes a string 'var' and converts it into a 'char**'
|
||||
* representing an argument list. The argument list can be consumed by
|
||||
* functions like 'execvp'.
|
||||
*
|
||||
* @param[in] var The string to be converted
|
||||
* @param[in] flags Flags controlling the behavior of the conversion
|
||||
*
|
||||
* @return A 'char**' representing the argument list
|
||||
*
|
||||
* The function uses the 'wordexp' function to perform the conversion. The
|
||||
* 'flags' argument controls the behavior of the 'wordexp' function. The
|
||||
* returned 'char**' is dynamically allocated, and must be freed by the
|
||||
* caller using 'delete[]'.
|
||||
*/
|
||||
char ** expand_env(const std::string& var, int flags = 0);
|
||||
|
||||
|
||||
#endif //LCPEX_STRING_EXPANSION_H
|
|
@ -0,0 +1,282 @@
|
|||
#include "libclpex_tty.h"
|
||||
|
||||
/**
|
||||
* @brief Safely prints a message and exits the program
|
||||
*
|
||||
* @param msg The message to print
|
||||
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
|
||||
*
|
||||
* This function takes a message `msg` and a pointer to a struct termios `ttyOrig` as input.
|
||||
* The function first prints the `msg` to `stderr`.
|
||||
* Then, it calls `ttyResetExit()` with `ttyOrig` as its argument to reset the terminal settings.
|
||||
* Finally, the function exits the program with exit code 1.
|
||||
*/
|
||||
void safe_perror( const char * msg, struct termios * ttyOrig )
|
||||
{
|
||||
std::cerr << msg << std::endl;
|
||||
ttyResetExit( ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Executes the child process
|
||||
*
|
||||
* @param fd_child_stderr_pipe A file descriptor array for the child process's stderr pipe
|
||||
* @param processed_command An array of char pointers representing the command and its arguments to be executed
|
||||
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
|
||||
* @param context_override A flag indicating whether to override the process's execution context
|
||||
* @param context_user The username to use for the execution context if `context_override` is `true`
|
||||
* @param context_group The group name to use for the execution context if `context_override` is `true`
|
||||
*
|
||||
* This function takes an array of file descriptors `fd_child_stderr_pipe` for the child process's stderr pipe,
|
||||
* an array of char pointers `processed_command` representing the command and its arguments to be executed,
|
||||
* a pointer to a struct termios `ttyOrig` representing the original terminal settings,
|
||||
* a flag `context_override` indicating whether to override the process's execution context,
|
||||
* a string `context_user` representing the username to use for the execution context if `context_override` is `true`,
|
||||
* and a string `context_group` representing the group name to use for the execution context if `context_override` is `true`.
|
||||
*
|
||||
* The function first redirects the child process's stderr to the write end of the stderr pipe.
|
||||
* If `context_override` is `true`, the function sets the process's execution context using `set_identity_context()`.
|
||||
* If `context_override` is `false`, the function does nothing.
|
||||
* Finally, the function executes the command specified in `processed_command` using `execvp()`.
|
||||
* If the execution of `execvp()` fails, the function calls `safe_perror()` to print a message and exit the program.
|
||||
*/
|
||||
void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], struct termios * ttyOrig, bool context_override, std::string context_user, std::string context_group )
|
||||
{
|
||||
// redirect stderr to the write end of the stderr pipe
|
||||
// close the file descriptor STDERR_FILENO if it was previously open, then (re)open it as a copy of
|
||||
while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
|
||||
|
||||
// close the write end of the stderr pipe
|
||||
close( fd_child_stderr_pipe[WRITE_END] );
|
||||
|
||||
// if the user has specified a context override, set the context
|
||||
if ( context_override )
|
||||
{
|
||||
int context_result = set_identity_context(context_user, context_group);
|
||||
switch (context_result) {
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
|
||||
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// execute the dang command, print to stdout, stderr (of parent), and dump to file for each!!!!
|
||||
// (and capture exit code in parent)
|
||||
int exit_code = execvp(processed_command[0], processed_command);
|
||||
safe_perror("failed on execvp in child", ttyOrig );
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
// this does three things:
|
||||
// - execute a dang string as a subprocess command
|
||||
// - capture child stdout/stderr to respective log files
|
||||
// - TEE child stdout/stderr to parent stdout/stderr
|
||||
int exec_pty(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
) {
|
||||
// initialize the terminal settings obj
|
||||
struct termios ttyOrig;
|
||||
|
||||
// if the user chose to supply the environment, then we need to clear the environment
|
||||
// before we fork the process, so that the child process will inherit the environment
|
||||
// from the parent process
|
||||
if ( environment_supplied ) {
|
||||
clearenv();
|
||||
}
|
||||
|
||||
// turn our command string into something execvp can consume
|
||||
char ** processed_command = expand_env(command );
|
||||
|
||||
// open file handles to the two log files we need to create for each execution
|
||||
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
|
||||
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
|
||||
|
||||
// create the pipes for the child process to write and read from using its stderr
|
||||
int fd_child_stderr_pipe[2];
|
||||
|
||||
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
|
||||
safe_perror( "child stderr pipe", &ttyOrig );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// using O_CLOEXEC to ensure that the child process closes the file descriptors
|
||||
if ( fcntl( fd_child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
||||
|
||||
// // same for stderr write
|
||||
if ( fcntl( fd_child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
|
||||
|
||||
// status result basket for the parent process to capture the child's exit status
|
||||
int status = 616;
|
||||
|
||||
// start ptyfork integration
|
||||
char slaveName[MAX_SNAME];
|
||||
int masterFd;
|
||||
struct winsize ws;
|
||||
|
||||
/* Retrieve the attributes of terminal on which we are started */
|
||||
if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
|
||||
safe_perror("tcgetattr", &ttyOrig);
|
||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
|
||||
safe_perror("ioctl-TIOCGWINSZ", &ttyOrig );
|
||||
|
||||
pid_t pid = ptyFork( &masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws );
|
||||
|
||||
switch( pid ) {
|
||||
case -1:
|
||||
{
|
||||
// fork failed
|
||||
safe_perror("ptyfork failure", &ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
case 0:
|
||||
{
|
||||
// child process
|
||||
run_child_process( fd_child_stderr_pipe, processed_command, &ttyOrig, context_override, context_user, context_group );
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// parent process
|
||||
// start ptyfork integration
|
||||
ttySetRaw(STDIN_FILENO, &ttyOrig);
|
||||
|
||||
|
||||
// The parent process has no need to access the entrance to the pipe
|
||||
close(fd_child_stderr_pipe[WRITE_END]);
|
||||
|
||||
// attempt to write to stdout,stderr from child as well as to write each to file
|
||||
char buf[BUFFER_SIZE];
|
||||
|
||||
// contains the byte count of the last read from the pipe
|
||||
ssize_t byte_count;
|
||||
|
||||
// watched_fds for poll() to wait on
|
||||
struct pollfd watched_fds[3];
|
||||
|
||||
// populate the watched_fds array
|
||||
|
||||
// child in to parent
|
||||
watched_fds[0].fd = STDIN_FILENO;
|
||||
watched_fds[0].events = POLLIN;
|
||||
|
||||
// child pty to parent (stdout)
|
||||
watched_fds[1].fd = masterFd;
|
||||
watched_fds[1].events = POLLIN;
|
||||
|
||||
// child stderr to parent
|
||||
watched_fds[2].fd = fd_child_stderr_pipe[READ_END];
|
||||
watched_fds[2].events = POLLIN;
|
||||
|
||||
// number of files poll() reports as ready
|
||||
int num_files_readable;
|
||||
|
||||
// loop flag
|
||||
bool break_out = false;
|
||||
|
||||
// loop until we've read all the data from the child process
|
||||
while ( ! break_out ) {
|
||||
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
|
||||
|
||||
if (num_files_readable == -1) {
|
||||
// error occurred in poll()
|
||||
safe_perror("poll", &ttyOrig );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (num_files_readable == 0) {
|
||||
// poll reports no files readable
|
||||
break_out = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int this_fd = 0; this_fd < 3; this_fd++) {
|
||||
if (watched_fds[this_fd].revents & POLLIN) {
|
||||
// this pipe is readable
|
||||
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
|
||||
|
||||
if (byte_count == -1) {
|
||||
// error reading from pipe
|
||||
safe_perror("read", &ttyOrig );
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (byte_count == 0) {
|
||||
// reached EOF on one of the streams but not a HUP
|
||||
// we've read all we can this cycle, so go to the next fd in the for loop
|
||||
continue;
|
||||
} else {
|
||||
// byte count was sane
|
||||
// write to stdout,stderr
|
||||
if (this_fd == 0) {
|
||||
// parent stdin received, write to child pty (stdin)
|
||||
write(masterFd, buf, byte_count);
|
||||
} else if (this_fd == 1 ) {
|
||||
// child pty sent some stuff, write to parent stdout and log
|
||||
write(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write(STDOUT_FILENO, buf, byte_count);
|
||||
} else if ( this_fd == 2 ){
|
||||
//the child's stderr pipe sent some stuff, write to parent stderr and log
|
||||
write(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write(STDERR_FILENO, buf, byte_count);
|
||||
} else {
|
||||
// this should never happen
|
||||
perror("Logic error!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLERR) {
|
||||
close(watched_fds[this_fd].fd);
|
||||
//break_out = true;
|
||||
continue;
|
||||
}
|
||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||
// this pipe has hung up
|
||||
close(watched_fds[this_fd].fd);
|
||||
break_out = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// close the log file handles
|
||||
fclose(stdout_log_fh);
|
||||
fclose(stderr_log_fh);
|
||||
ttyResetExit( &ttyOrig);
|
||||
if WIFEXITED(status) {
|
||||
return WEXITSTATUS(status);
|
||||
} else {
|
||||
return -617;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Created by phanes on 2/6/23.
|
||||
//
|
||||
|
||||
#ifndef LCPEX_LIBCLPEX_TTY_H
|
||||
#define LCPEX_LIBCLPEX_TTY_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <iostream>
|
||||
#include "../string_expansion/string_expansion.h"
|
||||
#include "../helpers.h"
|
||||
#include "../Contexts.h"
|
||||
#include "../vpty/pty_fork_mod/tty_functions.h"
|
||||
#include "../vpty/pty_fork_mod/pty_fork.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Execute a string as a subprocess command, capture its stdout/stderr to log files, and TEE its output to the parent process's stdout/stderr.
|
||||
* This function performs the following three tasks:
|
||||
* - Execute a string as a subprocess command.
|
||||
* - Capture the child process's stdout and stderr to respective log files.
|
||||
* - TEE the child process's stdout and stderr to the parent process's stdout and stderr.
|
||||
*
|
||||
* @param command The command to be executed as a subprocess.
|
||||
* @param stdout_log_file The file path where the child process's stdout will be logged.
|
||||
* @param stderr_log_file The file path where the child process's stderr will be logged.
|
||||
* @param context_override Specify whether to override the process's execution context.
|
||||
* @param context_user The user context to run the process as, if context_override is true.
|
||||
* @param context_group The group context to run the process as, if context_override is true.
|
||||
* @param environment_supplied Specify whether the environment is supplied.
|
||||
* @return The exit status of the child process. If the child process terminated due to a signal, returns -617.
|
||||
*/
|
||||
int exec_pty(
|
||||
std::string command,
|
||||
std::string stdout_log_file,
|
||||
std::string stderr_log_file,
|
||||
bool context_override,
|
||||
std::string context_user,
|
||||
std::string context_group,
|
||||
bool environment_supplied
|
||||
);
|
||||
|
||||
|
||||
#endif //LCPEX_LIBCLPEX_TTY_H
|
|
@ -0,0 +1,79 @@
|
|||
#include "pty_fork.h"
|
||||
|
||||
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
|
||||
const struct termios *slaveTermios, const struct winsize *slaveWS)
|
||||
{
|
||||
int mfd, slaveFd, savedErrno;
|
||||
pid_t childPid;
|
||||
char slname[MAX_SNAME];
|
||||
|
||||
mfd = ptyMasterOpen(slname, MAX_SNAME);
|
||||
if (mfd == -1)
|
||||
return -1;
|
||||
|
||||
if (slaveName != NULL) { /* Return slave name to caller */
|
||||
if (strlen(slname) < snLen) {
|
||||
strncpy(slaveName, slname, snLen);
|
||||
|
||||
} else { /* 'slaveName' was too small */
|
||||
close(mfd);
|
||||
errno = EOVERFLOW;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
childPid = fork();
|
||||
|
||||
/* fork() failed */
|
||||
if (childPid == -1) {
|
||||
/* close() might change 'errno' */
|
||||
savedErrno = errno;
|
||||
/* Don't leak file descriptors */
|
||||
close(mfd);
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (childPid != 0) { /* Parent */
|
||||
*masterFd = mfd; /* Only parent gets master fd */
|
||||
return childPid; /* Like parent of fork() */
|
||||
}
|
||||
|
||||
/* Child falls through to here */
|
||||
|
||||
if (setsid() == -1) /* Start a new session */
|
||||
perror("ptyFork:setsid");
|
||||
|
||||
close(mfd); /* Not needed in child */
|
||||
|
||||
slaveFd = open(slname, O_RDWR); /* Becomes controlling tty */
|
||||
if (slaveFd == -1)
|
||||
perror("ptyFork:open-slave");
|
||||
|
||||
#ifdef TIOCSCTTY /* Acquire controlling tty on BSD */
|
||||
if (ioctl(slaveFd, TIOCSCTTY, 0) == -1)
|
||||
perror("ptyFork:ioctl-TIOCSCTTY");
|
||||
#endif
|
||||
|
||||
if (slaveTermios != NULL) /* Set slave tty attributes */
|
||||
if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1)
|
||||
perror("ptyFork:tcsetattr");
|
||||
|
||||
if (slaveWS != NULL) /* Set slave tty window size */
|
||||
if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1)
|
||||
perror("ptyFork:ioctl-TIOCSWINSZ");
|
||||
|
||||
/* Duplicate pty slave to be child's stdin, stdout, and stderr */
|
||||
|
||||
if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO)
|
||||
perror("ptyFork:dup2-STDIN_FILENO");
|
||||
if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO)
|
||||
perror("ptyFork:dup2-STDOUT_FILENO");
|
||||
if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO)
|
||||
perror("ptyFork:dup2-STDERR_FILENO");
|
||||
|
||||
if (slaveFd > STDERR_FILENO) /* Safety check */
|
||||
close(slaveFd); /* No longer need this fd */
|
||||
|
||||
return 0; /* Like child of fork() */
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef LCPEX_PTY_FORK_H
|
||||
#define LCPEX_PTY_FORK_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include "./pty_master_open.h"
|
||||
#include <cstdio>
|
||||
#include "./tty_functions.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
/* Maximum size for pty slave name */
|
||||
#define MAX_SNAME 1000
|
||||
|
||||
/**
|
||||
* @brief Fork a child process with a pseudo-terminal
|
||||
*
|
||||
* This function forks a child process and returns the child's process ID. The
|
||||
* child process is attached to a pseudo-terminal and its standard input,
|
||||
* output, and error streams are redirected to the slave side of the pseudo-terminal.
|
||||
*
|
||||
* @param[out] masterFd Pointer to store the file descriptor of the master pty
|
||||
* @param[out] slaveName Buffer to store the name of the slave pty
|
||||
* @param[in] snLen Length of the 'slaveName' buffer
|
||||
* @param[in] slaveTermios Terminal attributes for the slave pty (optional)
|
||||
* @param[in] slaveWS Window size for the slave pty (optional)
|
||||
*
|
||||
* @return Process ID of the child process on success, or -1 on error
|
||||
*
|
||||
* On success, the file descriptor of the master pty is stored in the location
|
||||
* pointed to by 'masterFd', and the name of the slave pty is stored in the buffer
|
||||
* pointed to by 'slaveName'. If the buffer is too small to hold the name of the
|
||||
* slave, an error of type 'EOVERFLOW' is returned. If 'slaveTermios' is non-NULL,
|
||||
* the terminal attributes specified by it will be set for the slave pty. If
|
||||
* 'slaveWS' is non-NULL, the window size specified by it will be set for the slave
|
||||
* pty.
|
||||
*/
|
||||
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen, const struct termios *slaveTermios, const struct winsize *slaveWS);
|
||||
|
||||
|
||||
#endif //LCPEX_PTY_FORK_H
|
|
@ -0,0 +1,76 @@
|
|||
/* pty_master_open.cPP
|
||||
|
||||
Implement our ptyMasterOpen() function, based on UNIX 98 pseudoterminals.
|
||||
See comments below.
|
||||
|
||||
See also pty_master_open_bsd.c.
|
||||
*/
|
||||
#if ! defined(__sun)
|
||||
/* Prevents ptsname() declaration being visible on Solaris 8 */
|
||||
#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "pty_master_open.h" /* Declares ptyMasterOpen() */
|
||||
|
||||
/* Some implementations don't have posix_openpt() */
|
||||
|
||||
#if defined(__sun) /* Not on Solaris 8 */
|
||||
#define NO_POSIX_OPENPT
|
||||
#endif
|
||||
|
||||
#ifdef NO_POSIX_OPENPT
|
||||
static int
|
||||
posix_openpt(int flags)
|
||||
{
|
||||
return open("/dev/ptmx", flags);
|
||||
}
|
||||
|
||||
#endif
|
||||
/* Open a pty master, returning file descriptor, or -1 on error.
|
||||
On successful completion, the name of the corresponding pty
|
||||
slave is returned in 'slaveName'. 'snLen' should be set to
|
||||
indicate the size of the buffer pointed to by 'slaveName'. */
|
||||
|
||||
int ptyMasterOpen(char *slaveName, size_t snLen)
|
||||
{
|
||||
int masterFd, savedErrno;
|
||||
char *p;
|
||||
|
||||
masterFd = posix_openpt(O_RDWR | O_NOCTTY); /* Open pty master */
|
||||
if (masterFd == -1)
|
||||
return -1;
|
||||
|
||||
if (grantpt(masterFd) == -1) { /* Grant access to slave pty */
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (unlockpt(masterFd) == -1) { /* Unlock slave pty */
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
p = ptsname(masterFd); /* Get slave pty name */
|
||||
if (p == nullptr) {
|
||||
savedErrno = errno;
|
||||
close(masterFd); /* Might change 'errno' */
|
||||
errno = savedErrno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(p) < snLen) {
|
||||
strncpy(slaveName, p, snLen);
|
||||
} else { /* Return an error if buffer too small */
|
||||
close(masterFd);
|
||||
errno = EOVERFLOW;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return masterFd;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef LCPEX_PTY_MASTER_OPEN_H
|
||||
#define LCPEX_PTY_MASTER_OPEN_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <errno.h>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Open a pseudo-terminal master
|
||||
*
|
||||
* This function opens a pseudo-terminal master and returns the file descriptor
|
||||
* for the master. The name of the corresponding pseudo-terminal slave is also
|
||||
* returned in the buffer pointed to by 'slaveName'.
|
||||
*
|
||||
* @param[out] slaveName Buffer to store the name of the slave pty
|
||||
* @param[in] snLen Length of the 'slaveName' buffer
|
||||
*
|
||||
* @return File descriptor of the master pty on success, or -1 on error
|
||||
*
|
||||
* On success, the name of the corresponding pseudo-terminal slave is stored in
|
||||
* the buffer pointed to by 'slaveName'. If the buffer is too small to hold the
|
||||
* name of the slave, an error of type 'EOVERFLOW' is returned.
|
||||
*/
|
||||
int ptyMasterOpen(char *slaveName, size_t snLen);
|
||||
|
||||
#endif //LCPEX_PTY_MASTER_OPEN_H
|
|
@ -0,0 +1,77 @@
|
|||
#include "tty_functions.h"
|
||||
|
||||
/* Reset terminal mode on program exit */
|
||||
void ttyResetExit( struct termios * ttyOrig )
|
||||
{
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, ttyOrig) == -1)
|
||||
perror("tcsetattr");
|
||||
}
|
||||
|
||||
/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode
|
||||
with echoing turned off). This function assumes that the terminal is
|
||||
currently in cooked mode (i.e., we shouldn't call it if the terminal
|
||||
is currently in raw mode, since it does not undo all of the changes
|
||||
made by the ttySetRaw() function below). Return 0 on success, or -1
|
||||
on error. If 'prevTermios' is non-NULL, then use the buffer to which
|
||||
it points to return the previous terminal settings. */
|
||||
int ttySetCbreak(int fd, struct termios *prevTermios)
|
||||
{
|
||||
struct termios t;
|
||||
|
||||
if (tcgetattr(fd, &t) == -1)
|
||||
return -1;
|
||||
|
||||
if (prevTermios != nullptr)
|
||||
*prevTermios = t;
|
||||
|
||||
t.c_lflag &= ~(ICANON | ECHO);
|
||||
t.c_lflag |= ISIG;
|
||||
|
||||
t.c_iflag &= ~ICRNL;
|
||||
|
||||
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
|
||||
t.c_cc[VTIME] = 0; /* with blocking */
|
||||
|
||||
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Place terminal referred to by 'fd' in raw mode (noncanonical mode
|
||||
with all input and output processing disabled). Return 0 on success,
|
||||
or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to
|
||||
which it points to return the previous terminal settings. */
|
||||
|
||||
int ttySetRaw(int fd, struct termios *prevTermios)
|
||||
{
|
||||
struct termios t;
|
||||
|
||||
if (tcgetattr(fd, &t) == -1)
|
||||
return -1;
|
||||
|
||||
if (prevTermios != nullptr)
|
||||
*prevTermios = t;
|
||||
|
||||
t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
|
||||
/* Noncanonical mode, disable signals, extended
|
||||
input processing, and echoing */
|
||||
|
||||
t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
|
||||
INPCK | ISTRIP | IXON | PARMRK);
|
||||
/* Disable special handling of CR, NL, and BREAK.
|
||||
No 8th-bit stripping or parity error handling.
|
||||
Disable START/STOP output flow control. */
|
||||
|
||||
// this breaks cursor position change tracking in parent processes
|
||||
//t.c_oflag &= ~OPOST; /* Disable all output processing */
|
||||
|
||||
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
|
||||
t.c_cc[VTIME] = 0; /* with blocking */
|
||||
|
||||
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef LCPEX_TTY_FUNCTIONS_H
|
||||
#define LCPEX_TTY_FUNCTIONS_H
|
||||
|
||||
|
||||
#include <termios.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Place terminal referred to by 'fd' in cbreak mode
|
||||
*
|
||||
* This function places the terminal referred to by the file descriptor 'fd'
|
||||
* in cbreak mode (noncanonical mode with echoing turned off). It assumes that
|
||||
* the terminal is currently in cooked mode (i.e., it should not be called
|
||||
* if the terminal is currently in raw mode, since it does not undo all of
|
||||
* the changes made by the ttySetRaw() function).
|
||||
*
|
||||
* @param fd File descriptor of the terminal
|
||||
* @param prevTermios Buffer to store the previous terminal settings
|
||||
*
|
||||
* @return 0 on success, or -1 on error
|
||||
*
|
||||
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
|
||||
* to return the previous terminal settings.
|
||||
*/
|
||||
int ttySetCbreak(int fd, struct termios * prevTermios);
|
||||
|
||||
/**
|
||||
* @brief Place terminal referred to by 'fd' in raw mode
|
||||
*
|
||||
* This function places the terminal referred to by the file descriptor 'fd'
|
||||
* in raw mode.
|
||||
*
|
||||
* @param fd File descriptor of the terminal
|
||||
* @param prevTermios Buffer to store the previous terminal settings
|
||||
*
|
||||
* @return 0 on success, or -1 on error
|
||||
*
|
||||
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
|
||||
* to return the previous terminal settings.
|
||||
*/
|
||||
int ttySetRaw(int fd, struct termios * prevTermios);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reset terminal mode on program exit
|
||||
*
|
||||
* @param ttyOrig Original terminal mode to be restored
|
||||
*
|
||||
* This function resets the terminal mode when the program exits by using the
|
||||
* tcsetattr function. If the tcsetattr function returns an error, the perror
|
||||
* function is called to print an error message.
|
||||
*/
|
||||
void ttyResetExit( struct termios * ttyOrig );
|
||||
|
||||
|
||||
|
||||
#endif //LCPEX_TTY_FUNCTIONS_H
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
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 "Conf.h"
|
||||
|
||||
/// ConfigLoadException - General exception handler for the Conf class.
|
||||
class ConfigLoadException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit ConfigLoadException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit ConfigLoadException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~ConfigLoadException() throw (){}
|
||||
|
||||
/** 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:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/// Conf::Conf - Constructor for Conf type. Loads the configuration for the application.
|
||||
/// TODO Expand to detect when a directory path is supplied for units_path or plan_path and import all Tasks and Units.
|
||||
///
|
||||
/// \param filename - The filename to load the configuration from.
|
||||
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
std::string jval_s;
|
||||
bool jval_b;
|
||||
|
||||
// prepare context spaghetti
|
||||
this->override_context = false;
|
||||
|
||||
interpolate( filename);
|
||||
|
||||
try {
|
||||
// load the test file.
|
||||
this->load_json_file( filename );
|
||||
}
|
||||
catch (std::exception) {
|
||||
this->slog.log( E_FATAL, "Unable to locate configuration file: '" + filename + "'." );
|
||||
throw ConfigLoadException("Config file not found.");
|
||||
}
|
||||
|
||||
// find the path to the unit definitions file
|
||||
if (this->get_string(jval_s, "units_path") != 0 )
|
||||
{ throw ConfigLoadException("units_path string is not set in the config file supplied: " + filename); } else {
|
||||
interpolate( jval_s );
|
||||
this->units_path = jval_s;
|
||||
}
|
||||
jval_s = {0};
|
||||
|
||||
// find the path to logs directory
|
||||
if (this->get_string(jval_s, "logs_path") != 0 )
|
||||
{ throw ConfigLoadException("logs_path string is not set in the config file supplied: " + filename); } else {
|
||||
interpolate( jval_s );
|
||||
this->logs_path = jval_s;
|
||||
}
|
||||
jval_s = {0};
|
||||
|
||||
if (this->get_bool(jval_b, "execution_context_override") != 0 )
|
||||
{ throw ConfigLoadException("execution_context_override boolean is not set in the config file supplied: " + filename); } else {
|
||||
this->override_context = jval_b;
|
||||
}
|
||||
jval_b = {0};
|
||||
|
||||
if (this->get_string(jval_s, "execution_context") != 0 )
|
||||
{ throw ConfigLoadException("execution_context string is not set in the config file supplied: " + filename); } else {
|
||||
interpolate( jval_s );
|
||||
if ( ! is_dir( jval_s ) ) { throw ConfigLoadException( "The execution context supplied is an invalid directory."); } else {
|
||||
this->execution_context = jval_s;
|
||||
}
|
||||
}
|
||||
jval_s = {0};
|
||||
|
||||
};
|
||||
|
||||
/// Conf::has_context_override - Specifies whether or not the override context function is enabled in the Conf file.
|
||||
bool Conf::has_context_override() {
|
||||
return this->override_execution_context;
|
||||
}
|
||||
|
||||
/// Conf::get_execution_context - Specifies the path to the current working directory to set for all unit executions.
|
||||
std::string Conf::get_execution_context() {
|
||||
return this->execution_context;
|
||||
}
|
||||
|
||||
/// Conf::get_units_path - Retrieves the path to the Unit definition file from the application configuration file.
|
||||
std::string Conf::get_units_path() { return this->units_path; }
|
||||
|
||||
/// Conf::get_units_path - Retrieves the path to the Unit definition file from the application configuration file.
|
||||
std::string Conf::get_logs_path() { return this->logs_path; }
|
||||
|
||||
/// Conf::set_execution_context- Sets the execution context.
|
||||
void Conf::set_execution_context(std::string execution_context )
|
||||
{
|
||||
this->execution_context = execution_context;
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_CONF_H
|
||||
#define REX_CONF_H
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include <exception>
|
||||
#include "../../Logger/Logger.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
#define STRINGIZE2(s) #s
|
||||
#define STRINGIZE(s) STRINGIZE2(s)
|
||||
# define IMPL_CONFIG_VERSION 5
|
||||
# define VERSION_STRING STRINGIZE(IMPL_CONFIG_VERSION)
|
||||
|
||||
class Conf: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
std::string plan_path;
|
||||
std::string units_path;
|
||||
std::string execution_context;
|
||||
std::string logs_path;
|
||||
|
||||
// flag to indicate if execution context should be overriden in config file
|
||||
// if set to true rex should use whats in the config file for current working directory
|
||||
// if set to false, rex should use the current working directory at time of execution
|
||||
bool override_execution_context;
|
||||
|
||||
bool override_context;
|
||||
|
||||
public:
|
||||
Conf( std::string filename, int LOG_LEVEL );
|
||||
|
||||
bool has_context_override();
|
||||
|
||||
std::string get_units_path();
|
||||
std::string get_execution_context();
|
||||
std::string get_logs_path();
|
||||
|
||||
void set_execution_context( std::string );
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
|
||||
};
|
||||
|
||||
#endif //REX_CONF_H
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
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 "Plan.h"
|
||||
/// Plan_InvalidTaskIndex - Exception thrown when a Plan tries to access a contained Task's value by index not present
|
||||
/// in the Unit.
|
||||
class Plan_InvalidTaskIndex: public std::runtime_error { public:
|
||||
Plan_InvalidTaskIndex(): std::runtime_error("Plan: Attempted to access a Task using an invalid index.") {}
|
||||
};
|
||||
|
||||
/// Plan_InvalidTaskName - Exception thrown when a Plan tries to access a contained Task's value by name not present
|
||||
/// in the Unit.
|
||||
class Plan_InvalidTaskName: public std::runtime_error { public:
|
||||
Plan_InvalidTaskName(): std::runtime_error("Plan: Attempted to access a Task using an invalid name.") {}
|
||||
};
|
||||
|
||||
|
||||
/// Plan_Task_GeneralExecutionException - Wrapper exception to catch exceptions thrown by the execution of Tasks.
|
||||
class Plan_Task_GeneralExecutionException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit Plan_Task_GeneralExecutionException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit Plan_Task_GeneralExecutionException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~Plan_Task_GeneralExecutionException() throw (){}
|
||||
|
||||
/** 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:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/// Plan_Task_Missing_Dependency - Exception thrown when a Plan tries to access a contained Task's value by name not present
|
||||
/// in the Unit.
|
||||
class Plan_Task_Missing_Dependency: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit Plan_Task_Missing_Dependency(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit Plan_Task_Missing_Dependency(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~Plan_Task_Missing_Dependency() throw (){}
|
||||
|
||||
/** 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:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
/// Plan::Plan() - Constructor for Plan class. A Plan is a managed container for a Task vector. These tasks reference
|
||||
/// Units that are defined in the Units files (Suite). If Units are definitions, Tasks are selections of those
|
||||
/// definitions to execute, and if Units together form a Suite, Tasks together form a Plan.
|
||||
Plan::Plan(Conf * configuration, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_plan_" )
|
||||
{
|
||||
this->configuration = configuration;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// Plan::load_plan_file - Uses the json_root buffer on each run to append intact Units as they're deserialized from
|
||||
/// the provided file.
|
||||
///
|
||||
/// \param filename - The filename to load the plan from.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Plan::load_plan_file( std::string filename )
|
||||
{
|
||||
// plan always loads from file
|
||||
this->load_json_file( filename );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer wih a json::value object in the supplied filename
|
||||
if ( this->get_serialized( jbuff, "plan" ) == 0 )
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->tasks vector
|
||||
// buffer for tasks to append:
|
||||
Task tmp_T = Task( this->LOG_LEVEL );
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
tmp_T.load_root( this->json_root[ index ] );
|
||||
this->tasks.push_back( tmp_T );
|
||||
this->slog.log( LOG_INFO, "Added task \"" + tmp_T.get_name() + "\" to Plan." );
|
||||
}
|
||||
}
|
||||
|
||||
/// Plan::get_task - Retrieves a task by index.
|
||||
///
|
||||
/// \param result - The variable receiving the value.
|
||||
/// \param index - The numerical index in the Task vector to retrieve a value for.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Plan::get_task(Task & result, int index )
|
||||
{
|
||||
if ( index <= this->tasks.size() )
|
||||
{
|
||||
result = this->tasks[ index ];
|
||||
} else {
|
||||
throw Plan_InvalidTaskIndex();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Plan::load_definitions - Load the units corresponding to each task in plan from the given Suite.
|
||||
///
|
||||
/// \param unit_definitions - The Suite to load definitions from.
|
||||
/// \param verbose - Whether to print verbose information to STDOUT.
|
||||
void Plan::load_definitions( Suite unit_definitions )
|
||||
{
|
||||
// placeholder Unit
|
||||
Unit tmp_U = Unit( this->LOG_LEVEL );
|
||||
|
||||
// for every task in the plan:
|
||||
for (int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
// load the tmp_U corresponding to that task name
|
||||
unit_definitions.get_unit( tmp_U, this->tasks[i].get_name() );
|
||||
|
||||
// then have that task attach a copy of tmp_U
|
||||
this->tasks[i].load_definition( tmp_U );
|
||||
}
|
||||
}
|
||||
|
||||
/// Plan::get_task - Retrieves a task by name.
|
||||
///
|
||||
/// \param result - The variable receiving the value.
|
||||
/// \param provided_name - The name to find a task by.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Plan::get_task(Task & result, std::string provided_name )
|
||||
{
|
||||
bool foundMatch = false;
|
||||
|
||||
for ( int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
if ( this->tasks[i].get_name() == provided_name )
|
||||
{
|
||||
result = this->tasks[i];
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! foundMatch )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Task name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
throw Plan_InvalidTaskName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Plan::all_dependencies_complete
|
||||
///
|
||||
/// \param name - The name of the task in the plan to check met dependencies for.
|
||||
/// \return - boolean representation of whether all dependencies are complete or not.
|
||||
bool Plan::all_dependencies_complete(std::string name)
|
||||
{
|
||||
// get the task by name
|
||||
Task named_task = Task( this->LOG_LEVEL );
|
||||
this->get_task( named_task, name );
|
||||
|
||||
// get the dependencies of that task
|
||||
std::vector<std::string> deps = named_task.get_dependencies();
|
||||
|
||||
// create an empty task to assign values to during iteration
|
||||
Task tmpTask = Task( this->LOG_LEVEL );
|
||||
// iterate through its dependencies
|
||||
for ( int i = 0; i < deps.size(); i++ )
|
||||
{
|
||||
this->get_task( tmpTask, deps[i]);
|
||||
if (! tmpTask.is_complete() )
|
||||
{
|
||||
// error message?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Plan::execute() - Iterates through all tasks in a plan and executes them.
|
||||
///
|
||||
/// \param verbose
|
||||
void Plan::execute()
|
||||
{
|
||||
// for each task in this plan
|
||||
for ( int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
|
||||
{
|
||||
|
||||
this->slog.log( E_INFO, "[ '" + this->tasks[i].get_name() + "' ] Executing..." );
|
||||
try {
|
||||
this->tasks[i].execute( this->configuration );
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] Report: " + e.what() );
|
||||
throw Plan_Task_GeneralExecutionException("Could not execute task.");
|
||||
}
|
||||
} else {
|
||||
// not all deps met for this task
|
||||
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] This task was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
|
||||
throw Plan_Task_Missing_Dependency( "Unmet dependency for task." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_PLAN_H
|
||||
#define REX_PLAN_H
|
||||
|
||||
#include <string>
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Task.h"
|
||||
#include "Conf.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class Plan: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
// storage for the tasks that make up the plan
|
||||
std::vector<Task> tasks;
|
||||
Conf * configuration;
|
||||
|
||||
public:
|
||||
Plan(Conf * configuration, int LOG_LEVEL );
|
||||
|
||||
// append this->tasks from JSON file
|
||||
void load_plan_file( std::string filename );
|
||||
|
||||
// fetch a task from this->tasks
|
||||
void get_task( Task & result, std::string provided_name );
|
||||
|
||||
// fetch a task from this->tasks
|
||||
void get_task( Task & result, int index );
|
||||
|
||||
// load unit definitions from a provided suite and import them into individual tasks
|
||||
void load_definitions( Suite unit_definitions );
|
||||
|
||||
// fetch a corresponding Unit to a Task
|
||||
// void get_definition_from_task(Unit & result, Task input, bool verbose );
|
||||
|
||||
// execute all tasks in this plan
|
||||
void execute();
|
||||
|
||||
bool all_dependencies_complete(std::string name);
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //REX_PLAN_H
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_UNITS_H
|
||||
#define REX_UNITS_H
|
||||
|
||||
#include <vector>
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Unit.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
|
||||
class Suite: public JSON_Loader
|
||||
{
|
||||
protected:
|
||||
// storage for the definitions we are amassing from the unit definition files
|
||||
std::vector<Unit> units;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
Suite( int LOG_LEVEL );
|
||||
|
||||
// load a unit definitions file and add valid unit definitions to this->units
|
||||
void load_units_file( std::string filename );
|
||||
|
||||
// returns the unit identified by name
|
||||
void get_unit(Unit & result, std::string provided_name);
|
||||
|
||||
private:
|
||||
void get_units_from_dir( std::vector<std::string> * files, std::string path );
|
||||
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //REX_UNITS_H
|
|
@ -1,396 +0,0 @@
|
|||
/*
|
||||
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 "Task.h"
|
||||
|
||||
|
||||
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
||||
class Task_InvalidDataStructure: public std::runtime_error {
|
||||
public:
|
||||
Task_InvalidDataStructure(): std::runtime_error("Task: Attempted to access a member of a Task that is not set.") {}
|
||||
};
|
||||
|
||||
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
||||
class Task_NotReady: public std::runtime_error {
|
||||
public:
|
||||
Task_NotReady(): std::runtime_error("Task: Attempted to execute a Task whose Unit is not well defined.") {}
|
||||
};
|
||||
|
||||
|
||||
/// Task_RequiredButFailedTask - Exception thrown when a Task fails but should not.
|
||||
class TaskException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit TaskException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit TaskException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~TaskException() throw (){}
|
||||
|
||||
/** 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:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to
|
||||
/// execute, and its dependencies on other units to have already been completed successfully.
|
||||
Task::Task( int LOG_LEVEL ):
|
||||
slog( LOG_LEVEL, "_task_" ),
|
||||
definition( LOG_LEVEL )
|
||||
{
|
||||
// it hasn't executed yet.
|
||||
this->complete = false;
|
||||
|
||||
// it hasn't been matched with a definition yet.
|
||||
this->defined = false;
|
||||
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// Task::load_root() - loads json values to private members
|
||||
///
|
||||
/// \param loader_root - the Json::Value to populate from.
|
||||
/// \param verbose - Whether to print verbose information to STDOUT.
|
||||
void Task::load_root(Json::Value loader_root )
|
||||
{
|
||||
if ( loader_root.isMember("name") ) {
|
||||
this->name = loader_root.get("name", "?").asString();
|
||||
}
|
||||
else {
|
||||
throw Task_InvalidDataStructure();
|
||||
}
|
||||
|
||||
// fetch as Json::Value array obj
|
||||
Json::Value des_dep_root = loader_root.get("dependencies", 0);
|
||||
|
||||
// iterate through each member of that obj
|
||||
for ( int i = 0; i < des_dep_root.size(); i++ ) {
|
||||
// add each string to dependencies
|
||||
if ( des_dep_root[i].asString() != "" )
|
||||
{
|
||||
this->dependencies.push_back( des_dep_root[i].asString() );
|
||||
this->slog.log( E_INFO, "Added dependency \"" + des_dep_root[i].asString() + "\" to task \"" + this->get_name() + "\"." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Task::get_name - Retrieves the name of the current Task.
|
||||
std::string Task::get_name()
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
/// Task::load_definition - Loads a unit to a local member. Used to tie Units to Tasks.
|
||||
///
|
||||
/// \param selected_unit - The unit to attach.
|
||||
/// \param verbose - Whether to print to STDOUT.
|
||||
void Task::load_definition( Unit selected_unit )
|
||||
{
|
||||
this->definition = selected_unit;
|
||||
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan.");
|
||||
this->defined = true;
|
||||
}
|
||||
|
||||
/// Task::is_complete - Indicator if the task executed successfully.
|
||||
bool Task::is_complete()
|
||||
{
|
||||
return this->complete;
|
||||
}
|
||||
|
||||
|
||||
/// Task::mark_complete - Marks the task complete..
|
||||
void Task::mark_complete()
|
||||
{
|
||||
this->complete = true;
|
||||
}
|
||||
|
||||
|
||||
/// Task::get_dependencies - returns a pointer to the dependencies vector.
|
||||
std::vector<std::string> Task::get_dependencies()
|
||||
{
|
||||
return this->dependencies;
|
||||
}
|
||||
|
||||
/// Task::has_definition - Indicator if the task has attached its definition from a Suite.
|
||||
bool Task::has_definition()
|
||||
{
|
||||
return this->defined;
|
||||
}
|
||||
|
||||
/// Task::execute - execute a task's unit definition.
|
||||
/// See the design document for what flow control needs to look like here.
|
||||
/// \param verbose - Verbosity level - not implemented yet.
|
||||
void Task::execute( Conf * configuration )
|
||||
{
|
||||
// DUFFING - If rex is broken it's probably going to be in this block.
|
||||
// Somebody come clean this up, eh?
|
||||
|
||||
// PREWORK
|
||||
// throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay)
|
||||
std::ostringstream infostring;
|
||||
if ( ! this->has_definition() )
|
||||
{
|
||||
throw Task_NotReady();
|
||||
}
|
||||
|
||||
// get the name
|
||||
std::string task_name = this->definition.get_name();
|
||||
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Using unit definition: \"" + task_name + "\"." );
|
||||
// END PREWORK
|
||||
|
||||
// get the target execution command
|
||||
std::string target_command = configuration->get_execution_context() + "/" + this->definition.get_target();
|
||||
|
||||
// check if context override
|
||||
if ( configuration->has_context_override() )
|
||||
{
|
||||
// if so, set the CWD.
|
||||
chdir( configuration->get_execution_context().c_str() );
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Setting execution context: " + get_working_path() );
|
||||
}
|
||||
|
||||
|
||||
// a[0] execute target
|
||||
// TODO revise variable sourcing strategy
|
||||
// ....sourcing on the shell for variables and environment population doesn't have a good smell.
|
||||
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing target: \"" + target_command + "\"." );
|
||||
|
||||
std::string delimiter = " ";
|
||||
int space_index = target_command.find( delimiter );
|
||||
std::string bin_path = target_command.substr( 0, space_index );
|
||||
|
||||
if ( exists( bin_path ) )
|
||||
{
|
||||
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Target executable found.");
|
||||
} else {
|
||||
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Target executable does not exist." );
|
||||
throw Task_NotReady();
|
||||
}
|
||||
std::string static_env_file = configuration->get_execution_context() + "/" + this->definition.get_env_vars_file();
|
||||
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Vars file: " + static_env_file );
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Shell: " + this->definition.get_shell() );
|
||||
|
||||
|
||||
int return_code = Sproc::execute(
|
||||
this->definition.get_shell(),
|
||||
static_env_file,
|
||||
this->definition.get_user(),
|
||||
this->definition.get_group(),
|
||||
target_command,
|
||||
this->LOG_LEVEL,
|
||||
task_name,
|
||||
this->definition.get_stdout_log_flag(),
|
||||
configuration->get_logs_path()
|
||||
);
|
||||
|
||||
// **********************************************
|
||||
// d[0] Error Code Check
|
||||
// **********************************************
|
||||
if ( return_code == 0 )
|
||||
{
|
||||
// d[0].0 ZERO
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Target succeeded. Marking as complete." );
|
||||
|
||||
this->mark_complete();
|
||||
|
||||
// a[1] NEXT
|
||||
return;
|
||||
}
|
||||
|
||||
if ( return_code != 0 )
|
||||
{
|
||||
// d[0].1 NON-ZERO
|
||||
this->slog.log( E_WARN, "[ '" + task_name + "' ] Target failed with exit code " + std::to_string( return_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[1] Rectify Check
|
||||
// **********************************************
|
||||
if (! this->definition.get_rectify() )
|
||||
{
|
||||
// d[1].0 FALSE
|
||||
|
||||
// **********************************************
|
||||
// d[2] Required Check
|
||||
// **********************************************
|
||||
if (! this->definition.get_required() )
|
||||
{
|
||||
// d[2].0 FALSE
|
||||
// a[2] NEXT
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
} else {
|
||||
// d[2].1 TRUE
|
||||
// a[3] EXCEPTION
|
||||
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, and rectification is not enabled." );
|
||||
throw TaskException( "Task failed: " + task_name );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[2] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
|
||||
if ( this->definition.get_rectify() )
|
||||
{
|
||||
// d[1].1 TRUE (Rectify Check)
|
||||
this->slog.log( E_INFO, "[ " + task_name + " ] Rectification pattern is enabled." );
|
||||
|
||||
// a[4] Execute RECTIFIER
|
||||
std::string rectifier_command = this->definition.get_rectifier();
|
||||
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing rectification: " + rectifier_command + "." );
|
||||
|
||||
int rectifier_error = Sproc::execute(
|
||||
this->definition.get_shell(),
|
||||
static_env_file,
|
||||
this->definition.get_user(),
|
||||
this->definition.get_group(),
|
||||
rectifier_command,
|
||||
this->LOG_LEVEL,
|
||||
task_name,
|
||||
this->definition.get_stdout_log_flag(),
|
||||
configuration->get_logs_path()
|
||||
);
|
||||
|
||||
// **********************************************
|
||||
// d[3] Error Code Check for Rectifier
|
||||
// **********************************************
|
||||
if ( rectifier_error != 0 )
|
||||
{
|
||||
// d[3].1 Non-Zero
|
||||
this->slog.log( E_WARN, "[ '" + task_name + "' ] Rectification failed with exit code " + std::to_string( rectifier_error ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[4] Required Check
|
||||
// **********************************************
|
||||
if ( ! this->definition.get_required() ) {
|
||||
// d[4].0 FALSE
|
||||
// a[5] NEXT
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this->definition.get_required() )
|
||||
{
|
||||
// d[4].1 TRUE
|
||||
// a[6] EXCEPTION
|
||||
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, but failed, and rectification failed. Lost cause." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[4] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
// d[3] check exit code of rectifier
|
||||
if ( rectifier_error == 0 )
|
||||
{
|
||||
// d[3].0 Zero
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Rectification returned successfully." );
|
||||
|
||||
// a[7] Re-execute Target
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-Executing target \"" + this->definition.get_target() + "\"." );
|
||||
|
||||
int retry_code = Sproc::execute(
|
||||
this->definition.get_shell(),
|
||||
static_env_file,
|
||||
this->definition.get_user(),
|
||||
this->definition.get_group(),
|
||||
target_command,
|
||||
this->LOG_LEVEL,
|
||||
task_name,
|
||||
this->definition.get_stdout_log_flag(),
|
||||
configuration->get_logs_path()
|
||||
);
|
||||
|
||||
// **********************************************
|
||||
// d[5] Error Code Check
|
||||
// **********************************************
|
||||
if ( retry_code == 0 )
|
||||
{
|
||||
// d[5].0 ZERO
|
||||
// a[8] NEXT
|
||||
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-execution was successful." );
|
||||
return;
|
||||
} else {
|
||||
// d[5].1 NON-ZERO
|
||||
this->slog.log( E_WARN, "[ '" + task_name + "' ] Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[6] Required Check
|
||||
// **********************************************
|
||||
if ( ! this->definition.get_required() )
|
||||
{
|
||||
// d[6].0 FALSE
|
||||
// a[9] NEXT
|
||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this->definition.get_required() )
|
||||
{
|
||||
// d[6].1 TRUE
|
||||
// a[10] EXCEPTION
|
||||
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[6] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// **********************************************
|
||||
// end d[1] Rectify Check
|
||||
// **********************************************
|
||||
}
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
/*
|
||||
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"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
/// UnitException -
|
||||
class UnitException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit UnitException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit UnitException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~UnitException() throw (){}
|
||||
|
||||
/** 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:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
/// Unit::Unit - Constructor for Unit type. 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 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.
|
||||
///
|
||||
/// There is also:
|
||||
/// 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 in the rectifier runs.
|
||||
Unit::Unit( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_unit_" )
|
||||
{
|
||||
this->LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// 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("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 activation 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("log") )
|
||||
{ this->stdout_log_flag = loader_root.get("log", errmsg).asBool(); } else
|
||||
throw UnitException("No log 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.");
|
||||
|
||||
// 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) ) == NULL )
|
||||
{
|
||||
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 ) ) == NULL )
|
||||
{
|
||||
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( "shell" ) )
|
||||
{ this->shell = loader_root.get( "shell", errmsg ).asString(); } else this->shell = "/usr/bin/env sh";
|
||||
|
||||
if ( loader_root.isMember( "environment") )
|
||||
{ this->env_vars_file = loader_root.get( "environment", errmsg ).asString(); } else {
|
||||
throw UnitException("No environment file specified for a unit, and environment files are required for unit definitions.");
|
||||
}
|
||||
|
||||
this->populated = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Unit::load_string - populates a Unit object from a supplies JSON-formatted string. It's stellar.
|
||||
///
|
||||
/// \param json_val - JSON-formatted string to populate from. See Unit::load_root() for details on required structure.
|
||||
/// \return - The bool representation of success or failure.
|
||||
int Unit::load_string(std::string json_val)
|
||||
{
|
||||
// serialize
|
||||
this->load_json_string( json_val );
|
||||
|
||||
// deserialize
|
||||
this->load_root( this->json_root );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Unit::get_name - retrieves the name of the unit.
|
||||
///
|
||||
/// \return the name of the unit.
|
||||
std::string Unit::get_name()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->name;
|
||||
}
|
||||
|
||||
/// Unit::get_target - retrieves the target of the unit.
|
||||
///
|
||||
/// \return the target of the unit.
|
||||
std::string Unit::get_target()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->target;
|
||||
}
|
||||
|
||||
/// Unit::get_output - retrieves the output of the unit.
|
||||
///
|
||||
/// \return the output of the unit.
|
||||
std::string Unit::get_output()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->output;
|
||||
}
|
||||
|
||||
/// Unit::get_rectifier - retrieves the rectifier of the unit.
|
||||
///
|
||||
/// \return the rectifier of the unit.
|
||||
std::string Unit::get_rectifier()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->rectifier;
|
||||
}
|
||||
|
||||
/// Unit::get_active - retrieves the armed status of the unit.
|
||||
///
|
||||
/// \return the armed status of the unit.
|
||||
bool Unit::get_active()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->active;
|
||||
}
|
||||
|
||||
/// Unit::get_required - retrieves the requirement status of the unit.
|
||||
///
|
||||
/// \return the requirement status of the unit.
|
||||
bool Unit::get_required()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->required;
|
||||
}
|
||||
|
||||
/// Unit::get_rectify - retrieves the rectification status of the unit.
|
||||
///
|
||||
/// \return the rectification status of the unit.
|
||||
bool Unit::get_rectify()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->rectify;
|
||||
}
|
||||
|
||||
/// Unit::get_user - retrieves the user context for the unit.
|
||||
///
|
||||
/// \return the string value of the user name.
|
||||
std::string Unit::get_user()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->user;
|
||||
}
|
||||
/// Unit::get_group - retrieves the group context for the unit.
|
||||
///
|
||||
/// \return the string value of the group name.
|
||||
std::string Unit::get_group()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->group;
|
||||
}
|
||||
|
||||
/// Unit::get_shell - retrieves the shell path to use for the unit execution.
|
||||
///
|
||||
/// \return the string value of the shell path.
|
||||
std::string Unit::get_shell()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->shell;
|
||||
}
|
||||
|
||||
/// Unit::get_env_vars_file - retrieves the file path to use for the unit environment file. This is a file that is
|
||||
/// sourced by the chosen shell to populate any environment variables.
|
||||
/// \return the string value of the shell path.
|
||||
std::string Unit::get_env_vars_file()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->env_vars_file;
|
||||
}
|
||||
|
||||
/// Unit::get_stdout_log_flag() - retrieves the file path to use for the unit environment file. This is a file that is
|
||||
/// sourced by the chosen shell to populate any environment variables.
|
||||
/// \return the string value of the shell path.
|
||||
bool Unit::get_stdout_log_flag()
|
||||
{
|
||||
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||
return this->stdout_log_flag;
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
/* Unit.h
|
||||
* Unit is a type that represents a safely deserialized JSON object which defines what actions are taken as rex
|
||||
* iterates through it's Tasks in it's given Plan. They only define the behaviour on execution, while the tasks define
|
||||
* which Units are executed and in what order (and which Units a given Task depends on.
|
||||
*/
|
||||
#ifndef FTEST_UNIT_H
|
||||
#define FTEST_UNIT_H
|
||||
#include <string>
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class Unit: JSON_Loader
|
||||
{
|
||||
private:
|
||||
// the name of the test
|
||||
std::string name;
|
||||
|
||||
// the path of the executable this test executes when run
|
||||
std::string target;
|
||||
|
||||
// the desired output
|
||||
// poll: would an empty value be good to indicate to rely solely on zero/non-zero exit code?
|
||||
std::string output;
|
||||
|
||||
// the path of the executable this test runs when the target executable fails to produce output or a 0 exit code.
|
||||
std::string rectifier;
|
||||
|
||||
// an indicator of whether the test is active or not
|
||||
// this is used as a way to give definers a way to force executors to edit arbitrary fields or prevent
|
||||
// execution of potentially dangerous or intrusive tests
|
||||
bool active;
|
||||
|
||||
// an indicator of whether or not this test is required to pass.
|
||||
// intended to be used as a flag to halt execution of further tests on failure
|
||||
bool required;
|
||||
|
||||
// indicator of whether the rectifier executable should be run on test failures.
|
||||
// if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required
|
||||
bool rectify;
|
||||
|
||||
//indicator of whether stdout should log to file. used mainly to handle glitchy TUI systems when logs are being tailed.
|
||||
bool stdout_log_flag;
|
||||
|
||||
// user to run process as.
|
||||
// not intended for protected accounts, handle your own security
|
||||
std::string user;
|
||||
|
||||
// group to run process as.
|
||||
// not intended for protected accounts, handle your own security
|
||||
std::string group;
|
||||
|
||||
// shell to use for env
|
||||
std::string shell;
|
||||
|
||||
std::string env_vars_file;
|
||||
|
||||
public:
|
||||
Unit( int LOG_LEVEL );
|
||||
|
||||
// loads a serialized jason::value object as a unit
|
||||
int load_root( Json::Value loader_root );
|
||||
|
||||
// loads a string as a json string and deserializes that.
|
||||
int load_string( std::string json_val );
|
||||
|
||||
// getters
|
||||
std::string get_name();
|
||||
std::string get_target();
|
||||
std::string get_output();
|
||||
std::string get_rectifier();
|
||||
std::string get_env_vars_file();
|
||||
|
||||
bool get_active();
|
||||
bool get_required();
|
||||
bool get_rectify();
|
||||
bool get_stdout_log_flag();
|
||||
std::string get_user();
|
||||
std::string get_group();
|
||||
std::string get_shell();
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //FTEST_UNIT_H
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
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 "loaders.h"
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
#ifndef REX_LOADERS_H
|
||||
#define REX_LOADERS_H
|
||||
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Suite.h"
|
||||
#include "Plan.h"
|
||||
#include "Conf.h"
|
||||
|
||||
#endif //REX_LOADERS_H
|
|
@ -1,206 +0,0 @@
|
|||
/*
|
||||
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 "JSON_Loader.h"
|
||||
|
||||
/// JSON_Loader_NotReady - Exception thrown when a member function is called before data is populated.
|
||||
class JSON_Loader_NotReady: public std::runtime_error { public:
|
||||
JSON_Loader_NotReady(): std::runtime_error("JSON_Loader: Tried to access JSON without actually populating JSON.") {}
|
||||
};
|
||||
|
||||
/// JSON_Loader_FileNotfound - Exception thrown when JSON_Loader can not find the file it is told to parse.
|
||||
class JSON_Loader_FileNotFound: public std::runtime_error { public:
|
||||
JSON_Loader_FileNotFound(): std::runtime_error("JSON_Loader: The requested file could not be found.") {}
|
||||
};
|
||||
|
||||
/// JSON_Loader_InvalidJSON - Exception thrown when JSON_Loader fails to parse the JSON provided.
|
||||
class JSON_Loader_InvalidJSON: public std::runtime_error { public:
|
||||
JSON_Loader_InvalidJSON(): std::runtime_error("JSON_Loader: The JSON provided could not be parsed.") {}
|
||||
};
|
||||
|
||||
/// JSON_Loader::JSON_Loader - Constructor for JSON_Loader base class. Simply inits to an unpopulated state.
|
||||
///
|
||||
/// The JSON_Loader type is a base type. It is meant to provide the functionalities shared between Suite and Plan.
|
||||
JSON_Loader::JSON_Loader( int LOG_LEVEL ): slog( LOG_LEVEL, "_json_" )
|
||||
{
|
||||
this->populated = false;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// JSON_Loader::load_json_string - loads json from std::string into a json::value type and sets to protected member
|
||||
/// 'json_root'.
|
||||
///
|
||||
/// \param input - The JSON-formatted string to serialize
|
||||
/// \param verbose - Whether or not to print verbose information to STDOUT.
|
||||
void JSON_Loader::load_json_string( std::string input )
|
||||
{
|
||||
// reads from a string into a Json::Value type.
|
||||
Json::Reader json_reader;
|
||||
|
||||
// the deserialized json type to contain what's read by the reader
|
||||
// Json::Value json_root;
|
||||
|
||||
// create the ifstream file handle
|
||||
std::ifstream json_file_ifstream( input.c_str(), std::ifstream::binary );
|
||||
|
||||
// use the reader to parse the ifstream to the local property
|
||||
bool parsingSuccessful = json_reader.parse( json_file_ifstream, this->json_root );
|
||||
|
||||
if (! parsingSuccessful )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Failed to parse adhoc JSON value: " + json_reader.getFormattedErrorMessages() );
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
this->slog.log( E_DEBUG, "Successfully parsed JSON string with " + std::to_string( this->json_root.size() ) + "elements. Value: '" + input + "'." );
|
||||
}
|
||||
// flag as ready for consumption
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
/// JSON_Loader::load_json_file - Loads JSON from a filepath into a serialized representation assigned as a local member
|
||||
/// intended to be used as a buffer for further operations by base methods and derived class methods.
|
||||
///
|
||||
/// \param filename -
|
||||
/// \param verbose
|
||||
void JSON_Loader::load_json_file( std::string filename )
|
||||
{
|
||||
// reads from a file into a Json::Value type.
|
||||
Json::Reader json_reader;
|
||||
|
||||
// the a deserialized json type to contain what's read by the reader
|
||||
Json::Value json_root;
|
||||
|
||||
// first, check if the file exists
|
||||
if (! exists( filename ) )
|
||||
{
|
||||
this->slog.log( E_FATAL, "File '" + filename + "' does not exist." );
|
||||
throw JSON_Loader_FileNotFound();
|
||||
}
|
||||
|
||||
// create the ifstream file handle
|
||||
std::ifstream json_file_ifstream( filename, std::ifstream::binary );
|
||||
|
||||
// use the reader to parse the ifstream to the local property
|
||||
bool parsingSuccessful = json_reader.parse( json_file_ifstream, this->json_root );
|
||||
|
||||
if (! parsingSuccessful )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Failed to parse file '" + filename + "': " + json_reader.getFormattedErrorMessages() );
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
// if in verbose mode, give the user an "it worked" message
|
||||
this->slog.log( E_DEBUG, "Parsed '" + filename + "' with " + std::to_string( this->json_root.size() ) + " element(s)." );
|
||||
}
|
||||
// Flag as ready for consumption.
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
|
||||
/// JSON_Loader::as_string - returns the string representation of json_root
|
||||
std::string JSON_Loader::as_string()
|
||||
{
|
||||
if ( ! this->populated ) { throw JSON_Loader_NotReady(); }
|
||||
|
||||
return this->json_root.asString();
|
||||
}
|
||||
|
||||
/// JSON_Loader::get_serialized - assigns the serialized representation of the value of a key (json::value)
|
||||
///
|
||||
/// \param input - A reference to the json::value object to receive the new value.
|
||||
/// \param key - The JSON key name to assign the value to (the root of the json::value object by name)
|
||||
/// \param verbose - Whether or not to print verbose output to STDOUT.
|
||||
/// \return - Boolean indicator of success or failure (0|1)
|
||||
int JSON_Loader::get_serialized( Json::Value &input, std::string key )
|
||||
{
|
||||
// throw if the class is not ready to be used.
|
||||
if ( ! this->populated ) { throw JSON_Loader_NotReady(); }
|
||||
|
||||
if ( this->json_root.isMember( key ) )
|
||||
{
|
||||
// key was found so return it to the passed input ref
|
||||
input = this->json_root[ key ];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// key was not found
|
||||
|
||||
// verbose mode tells the user what key we were looking for.
|
||||
this->slog.log( E_FATAL, "Failed to find key '" + key + "'." );
|
||||
|
||||
// exit code for failure
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// JSON_Loader::get_string - assigns the serialized representation of the value of a key (json::value)
|
||||
///
|
||||
/// \param input - A reference to the json::value object to receive the new value.
|
||||
/// \param key - The JSON key name to assign the value to (the root of the json::value object by name)
|
||||
/// \param verbose - Whether or not to print verbose output to STDOUT.
|
||||
/// \return - Boolean indicator of success or failure (0|1)
|
||||
int JSON_Loader::get_string( std::string &input, std::string key )
|
||||
{
|
||||
// throw if the class is not ready to be used.
|
||||
if ( ! this->populated ) { throw JSON_Loader_NotReady(); }
|
||||
|
||||
if ( this->json_root.isMember( key ) )
|
||||
{
|
||||
// key was found so return it to the passed input ref
|
||||
input = this->json_root[ key ].asString();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// key was not found
|
||||
|
||||
// verbose mode tells the user what key we were looking for.
|
||||
this->slog.log( E_FATAL, "Failed to find key '" + key + "'." );
|
||||
|
||||
// exit code for failure
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// JSON_Loader::get_bool - assigns the serialized representation of the value of a key (json::value)
|
||||
///
|
||||
/// \param input - A reference to the json::value object to receive the new value.
|
||||
/// \param key - The JSON key name to assign the value to (the root of the json::value object by name)
|
||||
/// \param verbose - Whether or not to print verbose output to STDOUT.
|
||||
/// \return - Boolean indicator of success or failure (0|1)
|
||||
int JSON_Loader::get_bool( bool & input, std::string key )
|
||||
{
|
||||
// throw if the class is not ready to be used.
|
||||
if ( ! this->populated ) { throw JSON_Loader_NotReady(); }
|
||||
|
||||
if ( this->json_root.isMember( key ) )
|
||||
{
|
||||
// key was found so return it to the passed input ref
|
||||
input = this->json_root[ key ].asBool();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// key was not found
|
||||
|
||||
// verbose mode tells the user what key we were looking for.
|
||||
this->slog.log( E_FATAL, "Failed to find key '" + key + "'." );
|
||||
|
||||
// exit code for failure
|
||||
return 1;
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
#ifndef REX_JLOADER_H
|
||||
#define REX_JLOADER_H
|
||||
#include "../../json/json.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include "../misc/helpers.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class JSON_Loader
|
||||
{
|
||||
protected:
|
||||
Json::Value json_root;
|
||||
bool populated;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
JSON_Loader( int LOG_LEVEL );
|
||||
|
||||
// load from json file
|
||||
void load_json_file( std::string filename );
|
||||
|
||||
// load from std::string json
|
||||
void load_json_string( std::string input );
|
||||
|
||||
// return as a JSONCPP serialized object
|
||||
// deprecated -- these aren't really used.
|
||||
// Json::Value as_serialized();
|
||||
std::string as_string();
|
||||
|
||||
// safely handle deserialized type retrieval (if we want it to be safe)
|
||||
int get_serialized( Json::Value & input, std::string key );
|
||||
int get_string(std::string & input, std::string key );
|
||||
int get_bool(bool & input, std::string key );
|
||||
|
||||
private:
|
||||
Logger slog;
|
||||
int LOG_LEVEL;
|
||||
};
|
||||
#endif //REX_JLOADER_H
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
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 "helpers.h"
|
||||
|
||||
bool exists(const std::string& name)
|
||||
{
|
||||
struct stat buffer;
|
||||
return (stat (name.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
std::string get_working_path()
|
||||
{
|
||||
char temp[MAXPATHLEN];
|
||||
return ( getcwd(temp, MAXPATHLEN) ? std::string( temp ) : std::string("") );
|
||||
}
|
||||
|
||||
bool is_file( std::string path)
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISREG(buf.st_mode);
|
||||
}
|
||||
|
||||
bool is_dir( std::string path )
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISDIR(buf.st_mode);
|
||||
}
|
||||
|
||||
std::string get_8601()
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto itt = std::chrono::system_clock::to_time_t(now);
|
||||
std::ostringstream ss;
|
||||
// ss << std::put_time(gmtime(&itt), "%FT%TZ");
|
||||
ss << std::put_time(localtime(&itt), "%Y-%m-%d_%H:%M:%S");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void interpolate( std::string & text )
|
||||
{
|
||||
static std::regex env( "\\$\\{([^}]+)\\}" );
|
||||
std::smatch match;
|
||||
while ( std::regex_search( text, match, env ) )
|
||||
{
|
||||
const char * s = getenv( match[1].str().c_str() );
|
||||
const std::string var( s == NULL ? "" : s );
|
||||
text.replace( match[0].first, match[0].second, var );
|
||||
}
|
||||
|
||||
}
|
|
@ -51,3 +51,8 @@ void Logger::log( int LOG_LEVEL, std::string msg )
|
|||
}
|
||||
}
|
||||
|
||||
void Logger::log_task( int LOG_LEVEL, std::string task_name, std::string msg )
|
||||
{
|
||||
std::string final_msg = "[" + task_name + "] " + msg;
|
||||
this->log( LOG_LEVEL, final_msg );
|
||||
}
|
|
@ -26,7 +26,7 @@
|
|||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "../loaders/misc/helpers.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
enum L_LVL {
|
||||
E_FATAL,
|
||||
|
@ -36,13 +36,14 @@ enum L_LVL {
|
|||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
Logger( int LOG_LEVEL, std::string mask );
|
||||
void log( int LOG_LEVEL, std::string msg );
|
||||
public:
|
||||
Logger( int LOG_LEVEL, std::string mask );
|
||||
void log( int LOG_LEVEL, std::string msg );
|
||||
void log_task( int LOG_LEVEL, std::string task_name, std::string msg );
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
std::string mask;
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
std::string mask;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
#include "helpers.h"
|
||||
|
||||
/**
|
||||
* @brief Determines if a file or directory exists
|
||||
*
|
||||
* This function takes a file or directory name as input and uses the `stat` function to determine if the
|
||||
* specified file or directory exists.
|
||||
*
|
||||
* @param name The name of the file or directory to check
|
||||
*
|
||||
* @return `true` if the file or directory exists, `false` otherwise
|
||||
*/
|
||||
bool exists(const std::string& name)
|
||||
{
|
||||
struct stat buffer;
|
||||
return (stat (name.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the current working directory as a string
|
||||
*
|
||||
* This function uses the `getcwd` function to obtain the current working directory. If the `getcwd`
|
||||
* function fails, an empty string is returned.
|
||||
*
|
||||
* @return The current working directory as a string
|
||||
*/
|
||||
std::string get_working_path()
|
||||
{
|
||||
char temp[MAXPATHLEN];
|
||||
if (!getcwd(temp, MAXPATHLEN)) {
|
||||
return "";
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Determines if a given file path is a regular file
|
||||
*
|
||||
* This function takes a file path as input and uses the `stat` function to obtain information about the
|
||||
* file or directory. The `st_mode` member of the `stat` structure is then checked to determine if the
|
||||
* file is a regular file or not.
|
||||
*
|
||||
* @param path The file path to be checked
|
||||
*
|
||||
* @return `true` if the file path is a regular file, `false` otherwise
|
||||
*/
|
||||
bool is_file( std::string path)
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISREG(buf.st_mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Determines if a given file path is a directory
|
||||
*
|
||||
* This function takes a file path as input and uses the `stat` function to obtain information about the
|
||||
* file or directory. The `st_mode` member of the `stat` structure is then checked to determine if the
|
||||
* file is a directory or not.
|
||||
*
|
||||
* @param path The file path to be checked
|
||||
*
|
||||
* @return `true` if the file path is a directory, `false` otherwise
|
||||
*/
|
||||
bool is_dir( std::string path )
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISDIR(buf.st_mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns a string representation of the current date and time in the format "YYYY-MM-DD_HH:MM:SS"
|
||||
*
|
||||
* This function uses the `std::chrono` library to obtain the current system time and convert it to a
|
||||
* time_t value. It then uses the `strftime` function to format the date and time into a string with the
|
||||
* format "YYYY-MM-DD_HH:MM:SS". The resulting string is returned as the function result.
|
||||
*
|
||||
* @return A string representation of the current date and time
|
||||
*/
|
||||
std::string get_8601()
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto itt = std::chrono::system_clock::to_time_t(now);
|
||||
char buf[20];
|
||||
strftime(buf, sizeof(buf), "%Y-%m-%d_%H:%M:%S", localtime(&itt));
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Interpolates the environment variables in the input text
|
||||
*
|
||||
* This function takes a string reference as input and replaces all occurrences of
|
||||
* environment variables in the format `${VAR_NAME}` with their corresponding values.
|
||||
* If an environment variable is not set, it is replaced with an empty string.
|
||||
*
|
||||
* @param text The input text to be processed
|
||||
*/
|
||||
void interpolate( std::string & text )
|
||||
{
|
||||
static std::regex env( "\\$\\{([^}]+)\\}" );
|
||||
std::smatch match;
|
||||
while ( std::regex_search( text, match, env ) )
|
||||
{
|
||||
const char * s = getenv( match[1].str().c_str() );
|
||||
const std::string var( s == NULL ? "" : s );
|
||||
text.replace( match[0].first, match[0].second, var );
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#ifndef REX_HELPERS_H
|
||||
#define REX_HELPERS_H
|
||||
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
|
@ -50,4 +51,4 @@ std::string get_8601();
|
|||
|
||||
const char * command2args( std::string input_string );
|
||||
|
||||
#endif //REX_HELPERS_JH
|
||||
#endif //REX_HELPERS_H
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
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 "Plan.h"
|
||||
|
||||
|
||||
/**
|
||||
* @class Plan_InvalidTaskIndex
|
||||
* @brief Exception thrown when a Plan tries to access a contained Task's value by index not present in the Unit.
|
||||
*
|
||||
* This class is derived from std::runtime_error and is used to indicate that a Plan has tried to access a Task's value by index that is not present in the Unit.
|
||||
* The constructor creates an error message with the description "Plan: Attempted to access a Task using an invalid index."
|
||||
*/
|
||||
class Plan_InvalidTaskIndex : public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Plan_InvalidTaskIndex object with a default error message.
|
||||
*
|
||||
* The default error message is "Plan: Attempted to access a Task using an invalid index."
|
||||
*/
|
||||
Plan_InvalidTaskIndex() : std::runtime_error("Plan: Attempted to access a Task using an invalid index.") {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Plan_InvalidTaskName
|
||||
* @brief Exception thrown when a Plan tries to access a contained Task's value by name not present in the Unit.
|
||||
*
|
||||
* This class is derived from std::runtime_error and is used to indicate that a Plan has tried to access a Task's value by name that is not present in the Unit.
|
||||
* The constructor creates an error message with the description "Plan: Attempted to access a Task using an invalid name."
|
||||
*/
|
||||
class Plan_InvalidTaskName : public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Plan_InvalidTaskName object with a default error message.
|
||||
*
|
||||
* The default error message is "Plan: Attempted to access a Task using an invalid name."
|
||||
*/
|
||||
Plan_InvalidTaskName() : std::runtime_error("Plan: Attempted to access a Task using an invalid name.") {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Plan_Task_GeneralExecutionException
|
||||
* @brief Wrapper exception to catch exceptions thrown by the execution of Tasks.
|
||||
*
|
||||
* This class is derived from std::exception and is used as a wrapper to catch exceptions thrown by the execution of Tasks.
|
||||
* It has two constructors, one that takes a C-style string error message and another that takes a C++ STL string error message.
|
||||
* The error message is stored in the `msg_` member variable and can be accessed using the `what()` function.
|
||||
*/
|
||||
class Plan_Task_GeneralExecutionException : public std::exception {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Plan_Task_GeneralExecutionException object with a C-style string error message.
|
||||
*
|
||||
* The error message is passed as a C-style string and is copied upon construction.
|
||||
* The responsibility for deleting the char* lies with the caller.
|
||||
*
|
||||
* @param message C-style string error message.
|
||||
*/
|
||||
explicit Plan_Task_GeneralExecutionException(const char* message) : msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a Plan_Task_GeneralExecutionException object with a C++ STL string error message.
|
||||
*
|
||||
* The error message is passed as a C++ STL string.
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit Plan_Task_GeneralExecutionException(const std::string& message) : msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor.
|
||||
*
|
||||
* The destructor is virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~Plan_Task_GeneralExecutionException() throw() {}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the (constant) error description.
|
||||
*
|
||||
* Returns a pointer to the constant error description stored in the `msg_` member variable.
|
||||
* The underlying memory is in possession of the Exception object. Callers must not attempt to free the memory.
|
||||
*
|
||||
* @return A pointer to a const char*.
|
||||
*/
|
||||
virtual const char* what() const throw() { return msg_.c_str(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Plan_Task_Missing_Dependency
|
||||
* @brief Exception thrown when a Task is missing a required dependency.
|
||||
*
|
||||
* This class is derived from std::exception and is used to indicate that a Task is missing a required dependency.
|
||||
* It has two constructors, one that takes a C-style string error message and another that takes a C++ STL string error message.
|
||||
* The error message is stored in the `msg_` member variable and can be accessed using the `what()` function.
|
||||
*/
|
||||
class Plan_Task_Missing_Dependency : public std::exception {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Plan_Task_Missing_Dependency object with a C-style string error message.
|
||||
*
|
||||
* The error message is passed as a C-style string and is copied upon construction.
|
||||
* The responsibility for deleting the char* lies with the caller.
|
||||
*
|
||||
* @param message C-style string error message.
|
||||
*/
|
||||
explicit Plan_Task_Missing_Dependency(const char* message) : msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a Plan_Task_Missing_Dependency object with a C++ STL string error message.
|
||||
*
|
||||
* The error message is passed as a C++ STL string.
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit Plan_Task_Missing_Dependency(const std::string& message) : msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor.
|
||||
*
|
||||
* The destructor is virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~Plan_Task_Missing_Dependency() throw() {}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the (constant) error description.
|
||||
*
|
||||
* Returns a pointer to the constant error description stored in the `msg_` member variable.
|
||||
* The underlying memory is in possession of the Exception object. Callers must not attempt to free the memory.
|
||||
*
|
||||
* @return A pointer to a const char*.
|
||||
*/
|
||||
virtual const char* what() const throw() { return msg_.c_str(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructor for Plan class.
|
||||
*
|
||||
* A Plan is a managed container for a Task vector. These tasks reference Units that are defined in the Units files (Suite).
|
||||
* If Units are definitions, Tasks are selections of those definitions to execute, and if Units together form a Suite,
|
||||
* Tasks together form a Plan.
|
||||
*
|
||||
* @param configuration A pointer to a Conf object that holds the configuration information.
|
||||
* @param LOG_LEVEL The logging level for the plan.
|
||||
*/
|
||||
Plan::Plan(Conf * configuration, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_plan_" )
|
||||
{
|
||||
this->configuration = configuration;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Load the plan file and append intact Units as they're deserialized from the provided file.
|
||||
*
|
||||
* @param filename The filename to load the plan from.
|
||||
*/
|
||||
void Plan::load_plan_file( std::string filename )
|
||||
{
|
||||
// plan always loads from file
|
||||
this->load_json_file( filename );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer with a json::value object in the supplied filename
|
||||
if ( this->get_serialized( jbuff, "plan" ) == 0 )
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->tasks vector
|
||||
// buffer for tasks to append:
|
||||
Task tmp_T = Task( this->LOG_LEVEL );
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
tmp_T.load_root( this->json_root[ index ] );
|
||||
this->tasks.push_back( tmp_T );
|
||||
this->slog.log( LOG_INFO, "Added task \"" + tmp_T.get_name() + "\" to Plan." );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Retrieve a task by index.
|
||||
*
|
||||
* @param result The variable receiving the value.
|
||||
* @param index The numerical index in the Task vector to retrieve a value for.
|
||||
*
|
||||
* @throws Plan_InvalidTaskIndex if the index is greater than the size of the Task vector.
|
||||
*/
|
||||
void Plan::get_task(Task & result, int index )
|
||||
{
|
||||
if ( index <= this->tasks.size() )
|
||||
{
|
||||
result = this->tasks[ index ];
|
||||
} else {
|
||||
throw Plan_InvalidTaskIndex();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Load the units corresponding to each task in the plan from the given Suite.
|
||||
*
|
||||
* @param unit_definitions The Suite to load definitions from.
|
||||
*/
|
||||
void Plan::load_definitions( Suite unit_definitions )
|
||||
{
|
||||
// placeholder Unit
|
||||
Unit tmp_U = Unit( this->LOG_LEVEL );
|
||||
|
||||
// for every task in the plan:
|
||||
for (int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
// load the tmp_U corresponding to that task name
|
||||
unit_definitions.get_unit( tmp_U, this->tasks[i].get_name() );
|
||||
|
||||
// then have that task attach a copy of tmp_U
|
||||
this->tasks[i].load_definition( tmp_U );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Retrieve a task by name.
|
||||
*
|
||||
* @param result The variable receiving the value.
|
||||
* @param provided_name The name to find a task by.
|
||||
*
|
||||
* @throws Plan_InvalidTaskName if a task with the provided name is not found.
|
||||
*/
|
||||
void Plan::get_task(Task & result, std::string provided_name )
|
||||
{
|
||||
bool foundMatch = false;
|
||||
|
||||
for ( int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
if ( this->tasks[i].get_name() == provided_name )
|
||||
{
|
||||
result = this->tasks[i];
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! foundMatch )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Task name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
throw Plan_InvalidTaskName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Check whether all dependencies for a task with the given name are complete.
|
||||
*
|
||||
* @param name The name of the task in the plan to check met dependencies for.
|
||||
*
|
||||
* @return Boolean representation of whether all dependencies are complete or not.
|
||||
*/
|
||||
bool Plan::all_dependencies_complete(std::string name)
|
||||
{
|
||||
// get the task by name
|
||||
Task named_task = Task( this->LOG_LEVEL );
|
||||
this->get_task( named_task, name );
|
||||
|
||||
// get the dependencies of that task
|
||||
std::vector<std::string> deps = named_task.get_dependencies();
|
||||
|
||||
// create an empty task to assign values to during iteration
|
||||
Task tmpTask = Task( this->LOG_LEVEL );
|
||||
// iterate through its dependencies
|
||||
for ( int i = 0; i < deps.size(); i++ )
|
||||
{
|
||||
this->get_task( tmpTask, deps[i]);
|
||||
if (! tmpTask.is_complete() )
|
||||
{
|
||||
// error message?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Iterate through all tasks in the plan and execute them.
|
||||
*/
|
||||
void Plan::execute()
|
||||
{
|
||||
// for each task in this plan
|
||||
for ( int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
|
||||
{
|
||||
|
||||
this->slog.log( E_INFO, "[ '" + this->tasks[i].get_name() + "' ] Executing..." );
|
||||
try {
|
||||
this->tasks[i].execute( this->configuration );
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] Report: " + e.what() );
|
||||
throw Plan_Task_GeneralExecutionException("Could not execute task.");
|
||||
}
|
||||
} else {
|
||||
// not all deps met for this task
|
||||
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] This task was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
|
||||
throw Plan_Task_Missing_Dependency( "Unmet dependency for task." );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_PLAN_H
|
||||
#define REX_PLAN_H
|
||||
|
||||
#include "../json_support/JSON.h"
|
||||
#include "../logger/Logger.h"
|
||||
#include "../config/Config.h"
|
||||
#include "Task.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @class Plan
|
||||
* @brief A managed container for a Task vector.
|
||||
*
|
||||
* A Plan is a managed container for a Task vector. These tasks reference Units that are defined in the Units files (Suite).
|
||||
* If Units are definitions, Tasks are selections of those definitions to execute, and if Units together form a Suite, Tasks
|
||||
* together form a Plan.
|
||||
*/
|
||||
class Plan: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
// storage for the tasks that make up the plan
|
||||
std::vector<Task> tasks;
|
||||
Conf * configuration;
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for Plan class.
|
||||
*
|
||||
* @param configuration Configuration object.
|
||||
* @param LOG_LEVEL Logging level.
|
||||
*/
|
||||
Plan(Conf * configuration, int LOG_LEVEL );
|
||||
|
||||
/**
|
||||
* @brief Load the plan from a file and append tasks to the task vector.
|
||||
*
|
||||
* @param filename The filename to load the plan from.
|
||||
*/
|
||||
void load_plan_file( std::string filename );
|
||||
|
||||
/**
|
||||
* @brief Retrieve a task by name.
|
||||
*
|
||||
* @param result The variable receiving the value.
|
||||
* @param provided_name The name to find a task by.
|
||||
*
|
||||
* @throws Plan_InvalidTaskName if a task with the provided name is not found.
|
||||
*/
|
||||
void get_task( Task & result, std::string provided_name );
|
||||
|
||||
/**
|
||||
* @brief Retrieve a task by index.
|
||||
*
|
||||
* @param result The variable receiving the value.
|
||||
* @param index The numerical index in the Task vector to retrieve a value for.
|
||||
*
|
||||
* @throws Plan_InvalidTaskIndex if the index is greater than the size of the Task vector.
|
||||
*/
|
||||
void get_task( Task & result, int index );
|
||||
|
||||
/**
|
||||
* @brief Load the units corresponding to each task in the plan from the given Suite.
|
||||
*
|
||||
* @param unit_definitions The Suite to load definitions from.
|
||||
*/
|
||||
void load_definitions( Suite unit_definitions );
|
||||
|
||||
/**
|
||||
* @brief Check whether all dependencies for a task with the given name are complete.
|
||||
*
|
||||
* @param name The name of the task in the plan to check met dependencies for.
|
||||
*
|
||||
* @return Boolean representation of whether all dependencies are complete or not.
|
||||
*/
|
||||
bool all_dependencies_complete(std::string name);
|
||||
|
||||
/**
|
||||
* @brief Iterate through all tasks in the plan and execute them.
|
||||
*/
|
||||
void execute();
|
||||
};
|
||||
#endif //REX_PLAN_H
|
|
@ -0,0 +1,621 @@
|
|||
#include "Task.h"
|
||||
|
||||
/*
|
||||
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 "Task.h"
|
||||
|
||||
|
||||
/**
|
||||
* @class Task_InvalidDataStructure
|
||||
* @brief Exception thrown when a Task is defined with invalid JSON.
|
||||
*
|
||||
* This exception is derived from the standard runtime_error exception and is thrown
|
||||
* when a Task object is defined with an invalid JSON structure.
|
||||
*/
|
||||
class Task_InvalidDataStructure: public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Task_InvalidDataStructure object.
|
||||
*
|
||||
* This constructor creates a Task_InvalidDataStructure object with an error message
|
||||
* indicating that a task was attempted to be accessed with an invalid data structure.
|
||||
*/
|
||||
Task_InvalidDataStructure(): std::runtime_error("Task: Attempted to access a member of a Task that is not set.") {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Task_NotReady
|
||||
* @brief Exception thrown when a Task is not ready to be executed.
|
||||
*
|
||||
* This exception is derived from the standard runtime_error exception and is thrown
|
||||
* when a Task object is not well defined and cannot be executed.
|
||||
*/
|
||||
class Task_NotReady: public std::runtime_error {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Task_NotReady object.
|
||||
*
|
||||
* This constructor creates a Task_NotReady object with an error message
|
||||
* indicating that a task was attempted to be executed but its Unit was not well defined.
|
||||
*/
|
||||
Task_NotReady(): std::runtime_error("Task: Attempted to execute a Task whose Unit is not well defined.") {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class TaskException
|
||||
* @brief Exception thrown for errors related to Tasks.
|
||||
*
|
||||
* This exception is a base class for exceptions related to Tasks and is derived
|
||||
* from the standard exception class.
|
||||
*/
|
||||
class TaskException: public std::exception
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a TaskException object with a C-style string error message.
|
||||
*
|
||||
* This constructor creates a TaskException object with a C-style string error message.
|
||||
* The string contents are copied upon construction and the responsibility for deleting
|
||||
* the char* lies with the caller.
|
||||
*
|
||||
* @param message C-style string error message.
|
||||
*/
|
||||
explicit TaskException(const char* message): msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a TaskException object with a C++ STL string error message.
|
||||
*
|
||||
* This constructor creates a TaskException object with a C++ STL string error message.
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit TaskException(const std::string& message): msg_(message) {}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor.
|
||||
*
|
||||
* This destructor is virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~TaskException() throw () {}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the error description.
|
||||
*
|
||||
* This function returns a pointer to the error description.
|
||||
* The underlying memory is in posession of the Exception object and callers must not attempt
|
||||
* to free the memory.
|
||||
*
|
||||
* @return A pointer to a const char*.
|
||||
*/
|
||||
virtual const char* what() const throw () { return msg_.c_str(); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @class Task
|
||||
* @brief The building block of a Plan indicating of which Unit to execute and its dependencies.
|
||||
*
|
||||
* The Task class represents a single unit of work in a Plan. It specifies which Unit should be executed
|
||||
* and any dependencies that must be satisfied before it can be executed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Constructor for the Task class.
|
||||
*
|
||||
* This constructor initializes a Task object with a specified log level and creates log and definition objects.
|
||||
* The task is set as not complete and not defined by default.
|
||||
*
|
||||
* @param LOG_LEVEL The log level for this Task object.
|
||||
*/
|
||||
Task::Task( int LOG_LEVEL ):
|
||||
slog( LOG_LEVEL, "_task_" ),
|
||||
definition( LOG_LEVEL )
|
||||
{
|
||||
// it hasn't executed yet.
|
||||
this->complete = false;
|
||||
|
||||
// it hasn't been matched with a definition yet.
|
||||
this->defined = false;
|
||||
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Loads JSON values into private members.
|
||||
*
|
||||
* This function loads JSON values into the private members of a Task object.
|
||||
*
|
||||
* @param loader_root The Json::Value to populate from.
|
||||
* @throws Task_InvalidDataStructure if the "name" member is not present in the JSON object.
|
||||
*/
|
||||
void Task::load_root(Json::Value loader_root )
|
||||
{
|
||||
if ( loader_root.isMember("name") ) {
|
||||
this->name = loader_root.get("name", "?").asString();
|
||||
}
|
||||
else {
|
||||
throw Task_InvalidDataStructure();
|
||||
}
|
||||
|
||||
// fetch as Json::Value array obj
|
||||
Json::Value des_dep_root = loader_root.get("dependencies", 0);
|
||||
|
||||
// iterate through each member of that obj
|
||||
for ( int i = 0; i < des_dep_root.size(); i++ ) {
|
||||
// add each string to dependencies
|
||||
if ( des_dep_root[i].asString() != "" )
|
||||
{
|
||||
this->dependencies.push_back( des_dep_root[i].asString() );
|
||||
this->slog.log( E_INFO, "Added dependency \"" + des_dep_root[i].asString() + "\" to task \"" + this->get_name() + "\"." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the name of the current Task.
|
||||
*
|
||||
* @return The name of the Task as a string.
|
||||
*/
|
||||
std::string Task::get_name()
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads a unit to a local member. Used to tie Units to Tasks.
|
||||
*
|
||||
* @param selected_unit The unit to attach.
|
||||
* @param verbose Whether to print to STDOUT.
|
||||
*/
|
||||
void Task::load_definition( Unit selected_unit )
|
||||
{
|
||||
this->definition = selected_unit;
|
||||
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan.");
|
||||
this->defined = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Indicates if the task executed successfully.
|
||||
*
|
||||
* @return True if the task completed successfully, false otherwise.
|
||||
*/
|
||||
bool Task::is_complete()
|
||||
{
|
||||
return this->complete;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Marks the task as complete.
|
||||
*/
|
||||
void Task::mark_complete()
|
||||
{
|
||||
this->complete = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the dependencies vector.
|
||||
*
|
||||
* @return A pointer to the vector containing the task's dependencies.
|
||||
*/
|
||||
std::vector<std::string> Task::get_dependencies()
|
||||
{
|
||||
return this->dependencies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Indicates if the task has attached its definition from a Suite.
|
||||
*
|
||||
* @return True if the task has a definition, false otherwise.
|
||||
*/
|
||||
bool Task::has_definition()
|
||||
{
|
||||
return this->defined;
|
||||
}
|
||||
|
||||
bool is_abs_path( const std::string &path )
|
||||
{
|
||||
if ( path[0] == '/' )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool abspathExists(const std::string &path) {
|
||||
if ( is_abs_path( path ) )
|
||||
{
|
||||
struct stat buffer;
|
||||
return (stat(path.c_str(), &buffer) == 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string path_from_str( const std::string &inputString )
|
||||
{
|
||||
std::string result = "";
|
||||
|
||||
// Find the position of the first space character in the input string
|
||||
size_t spacePosition = inputString.find(' ');
|
||||
|
||||
// If there is a space in the input string, extract the substring up to the space
|
||||
if (spacePosition != std::string::npos) {
|
||||
result = inputString.substr(0, spacePosition);
|
||||
}
|
||||
// Otherwise, just return the entire input string
|
||||
else {
|
||||
result = inputString;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool createDirectory(const std::string& path) {
|
||||
// Check if the directory already exists
|
||||
struct stat info;
|
||||
if (stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode)) {
|
||||
return true; // Directory already exists
|
||||
}
|
||||
|
||||
// Create the directory recursively
|
||||
size_t pos = 0;
|
||||
std::string dir;
|
||||
while ((pos = path.find_first_of('/', pos + 1)) != std::string::npos) {
|
||||
dir = path.substr(0, pos);
|
||||
if (stat(dir.c_str(), &info) != 0) { // Directory does not exist
|
||||
if (mkdir(dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
|
||||
return false; // Failed to create directory
|
||||
}
|
||||
} else if (!S_ISDIR(info.st_mode)) {
|
||||
return false; // Path segment exists but is not a directory
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final directory
|
||||
if (mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
|
||||
return false; // Failed to create directory
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::prepare_logs( std::string task_name, std::string logs_root, std::string timestamp )
|
||||
{
|
||||
std::string full_path = logs_root + "/" + task_name;
|
||||
bool ret = false;
|
||||
ret = createDirectory( full_path );
|
||||
|
||||
if (ret)
|
||||
{
|
||||
this->slog.log_task( E_INFO, "LOG_CREATE", "Logging will be at '" + full_path + "'." );
|
||||
} else {
|
||||
this->slog.log_task( E_FATAL, "LOG_CREATE", "Creation of directory path '" + full_path + "' failed." );
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Task::execute - execute a task's unit definition.
|
||||
/// See the design document for what flow control needs to look like here.
|
||||
/// \param verbose - Verbosity level - not implemented yet.
|
||||
void Task::execute( Conf * configuration )
|
||||
{
|
||||
|
||||
if ( ! this->has_definition() )
|
||||
{
|
||||
throw Task_NotReady();
|
||||
}
|
||||
|
||||
bool override_working_dir = this->definition.get_set_working_directory();
|
||||
bool is_shell_command = this->definition.get_is_shell_command();
|
||||
bool supply_environment = this->definition.get_supply_environment();
|
||||
bool rectify = this->definition.get_rectify();
|
||||
bool active = this->definition.get_active();
|
||||
bool required = this->definition.get_required();
|
||||
bool set_user_context = this->definition.get_set_user_context();
|
||||
bool force_pty = this->definition.get_force_pty();
|
||||
|
||||
std::string task_name = this->definition.get_name();
|
||||
std::string command = this->definition.get_target();
|
||||
std::string shell_name = this->definition.get_shell_definition();
|
||||
Shell shell_definition = configuration->get_shell_by_name( shell_name );
|
||||
std::string new_working_dir = this->definition.get_working_directory();
|
||||
std::string rectifier = this->definition.get_rectifier();
|
||||
std::string user = this->definition.get_user();
|
||||
std::string group = this->definition.get_group();
|
||||
std::string environment_file = this->definition.get_environment_file();
|
||||
std::string logs_root = configuration->get_logs_path();
|
||||
|
||||
this->slog.log_task( E_DEBUG, task_name, "Using unit definition: \"" + task_name + "\"." );
|
||||
|
||||
// sanitize all path inputs from unit definition to be either absolute paths or relative to
|
||||
// project_root
|
||||
|
||||
if ( supply_environment )
|
||||
{
|
||||
if (! is_shell_command )
|
||||
{
|
||||
throw TaskException("Garbage input: Supplied a shell environment file for a non-shell target.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( ! active )
|
||||
{
|
||||
throw TaskException("Somehow tried to execute a task with an inactive unit definition.");
|
||||
}
|
||||
|
||||
if (! is_abs_path( path_from_str( command ) ) )
|
||||
{
|
||||
command = configuration->get_project_root() + "/" + command;
|
||||
}
|
||||
|
||||
if ( rectify )
|
||||
{
|
||||
if (! is_abs_path( path_from_str( rectifier ) ) )
|
||||
{
|
||||
rectifier = configuration->get_project_root() + "/" + rectifier;
|
||||
}
|
||||
}
|
||||
|
||||
if ( supply_environment )
|
||||
{
|
||||
if (! is_abs_path( environment_file ) )
|
||||
{
|
||||
environment_file = configuration->get_project_root() + "/" + environment_file;
|
||||
}
|
||||
}
|
||||
|
||||
if ( override_working_dir )
|
||||
{
|
||||
if (! is_abs_path( new_working_dir ) )
|
||||
{
|
||||
new_working_dir = configuration->get_project_root() + "/" + new_working_dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_abs_path( configuration->get_logs_path() ) )
|
||||
{
|
||||
logs_root = configuration->get_project_root() + "/" + logs_root;
|
||||
}
|
||||
|
||||
std::string timestamp = get_8601();
|
||||
// set these first so the pre-execution logs get there.
|
||||
/*
|
||||
* create the logs dir here
|
||||
*/
|
||||
|
||||
if (! this->prepare_logs( task_name, logs_root, timestamp ) )
|
||||
{
|
||||
throw TaskException("Could not prepare logs for task execution at '" + logs_root + "'.");
|
||||
}
|
||||
|
||||
std::string stdout_log_file = logs_root + "/" + timestamp + ".stdout.log";
|
||||
std::string stderr_log_file = logs_root + "/" + timestamp + ".stderr.log";
|
||||
|
||||
// check if working directory is to be set
|
||||
if ( override_working_dir )
|
||||
{
|
||||
// if so, set the CWD.
|
||||
chdir( new_working_dir.c_str() );
|
||||
this->slog.log_task( E_INFO, task_name, "Setting working directory: " + new_working_dir );
|
||||
}
|
||||
|
||||
if ( is_shell_command )
|
||||
{
|
||||
this->slog.log_task( E_INFO, task_name, "Vars file: " + environment_file );
|
||||
this->slog.log_task( E_INFO, task_name, "Shell: " + shell_definition.path );
|
||||
}
|
||||
|
||||
// a[0] execute target
|
||||
// TODO ...sourcing on the shell for variables and environment population doesn't have a good smell.
|
||||
// it does prevent unexpected behaviour from reimplementing what bash does though
|
||||
|
||||
this->slog.log_task( E_INFO, task_name, "Executing target: \"" + command + "\"." );
|
||||
|
||||
int return_code = lcpex(
|
||||
command,
|
||||
stdout_log_file,
|
||||
stderr_log_file,
|
||||
set_user_context,
|
||||
user,
|
||||
group,
|
||||
force_pty,
|
||||
is_shell_command,
|
||||
shell_definition.path,
|
||||
shell_definition.execution_arg,
|
||||
supply_environment,
|
||||
shell_definition.source_cmd,
|
||||
environment_file
|
||||
);
|
||||
|
||||
|
||||
// **********************************************
|
||||
// d[0] Error Code Check
|
||||
// **********************************************
|
||||
if ( return_code == 0 )
|
||||
{
|
||||
// d[0].0 ZERO
|
||||
this->slog.log_task( E_INFO, task_name, "Target succeeded. Marking as complete." );
|
||||
|
||||
this->mark_complete();
|
||||
|
||||
// a[1] NEXT
|
||||
return;
|
||||
}
|
||||
|
||||
if ( return_code != 0 )
|
||||
{
|
||||
// d[0].1 NON-ZERO
|
||||
this->slog.log_task( E_WARN, task_name, "Target failed with exit code " + std::to_string( return_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[1] Rectify Check
|
||||
// **********************************************
|
||||
if (! this->definition.get_rectify() )
|
||||
{
|
||||
// d[1].0 FALSE
|
||||
// **********************************************
|
||||
// d[2] Required Check
|
||||
// **********************************************
|
||||
if (! required )
|
||||
{
|
||||
// d[2].0 FALSE
|
||||
// a[2] NEXT
|
||||
this->slog.log_task( E_INFO, task_name, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
} else {
|
||||
// d[2].1 TRUE
|
||||
// a[3] EXCEPTION
|
||||
this->slog.log_task( E_FATAL, task_name, "Task is required, and failed, and rectification is not enabled." );
|
||||
throw TaskException( "Task failed: " + task_name );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[2] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
|
||||
if ( rectify )
|
||||
{
|
||||
// d[1].1 TRUE (Rectify Check)
|
||||
this->slog.log_task( E_INFO, task_name, "Rectification pattern is enabled." );
|
||||
|
||||
// a[4] Execute RECTIFIER
|
||||
this->slog.log_task( E_INFO, task_name, "Executing rectification: " + rectifier + "." );
|
||||
int rectifier_error = lcpex(
|
||||
rectifier,
|
||||
stdout_log_file,
|
||||
stderr_log_file,
|
||||
set_user_context,
|
||||
user,
|
||||
group,
|
||||
force_pty,
|
||||
is_shell_command,
|
||||
shell_definition.path,
|
||||
shell_definition.execution_arg,
|
||||
supply_environment,
|
||||
shell_definition.source_cmd,
|
||||
environment_file
|
||||
);
|
||||
|
||||
// **********************************************
|
||||
// d[3] Error Code Check for Rectifier
|
||||
// **********************************************
|
||||
if ( rectifier_error != 0 )
|
||||
{
|
||||
// d[3].1 Non-Zero
|
||||
this->slog.log_task( E_WARN, task_name, "Rectification failed with exit code " + std::to_string( rectifier_error ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[4] Required Check
|
||||
// **********************************************
|
||||
if ( ! required ) {
|
||||
// d[4].0 FALSE
|
||||
// a[5] NEXT
|
||||
this->slog.log_task( E_INFO, task_name, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
} else {
|
||||
// d[4].1 TRUE
|
||||
// a[6] EXCEPTION
|
||||
this->slog.log_task( E_FATAL, task_name, "Task is required, but failed, and rectification failed. Lost cause." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[4] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
// d[3] check exit code of rectifier
|
||||
if ( rectifier_error == 0 )
|
||||
{
|
||||
// d[3].0 Zero
|
||||
this->slog.log_task( E_INFO, task_name, "Rectification returned successfully." );
|
||||
|
||||
// a[7] Re-execute Target
|
||||
this->slog.log_task( E_INFO, task_name, "Re-Executing target '" + command + "'." );
|
||||
|
||||
int retry_code = lcpex(
|
||||
command,
|
||||
stdout_log_file,
|
||||
stderr_log_file,
|
||||
set_user_context,
|
||||
user,
|
||||
group,
|
||||
force_pty,
|
||||
is_shell_command,
|
||||
shell_definition.path,
|
||||
shell_definition.execution_arg,
|
||||
supply_environment,
|
||||
shell_definition.source_cmd,
|
||||
environment_file
|
||||
);
|
||||
|
||||
// **********************************************
|
||||
// d[5] Error Code Check
|
||||
// **********************************************
|
||||
if ( retry_code == 0 )
|
||||
{
|
||||
// d[5].0 ZERO
|
||||
// a[8] NEXT
|
||||
this->slog.log_task( E_INFO, task_name, "Re-execution was successful." );
|
||||
return;
|
||||
} else {
|
||||
// d[5].1 NON-ZERO
|
||||
this->slog.log_task( E_WARN, task_name, "Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[6] Required Check
|
||||
// **********************************************
|
||||
if ( ! required )
|
||||
{
|
||||
// d[6].0 FALSE
|
||||
// a[9] NEXT
|
||||
this->slog.log_task( E_INFO, task_name, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
} else {
|
||||
// d[6].1 TRUE
|
||||
// a[10] EXCEPTION
|
||||
this->slog.log_task( E_FATAL, task_name, "Task is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[6] Required Check
|
||||
// **********************************************
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// **********************************************
|
||||
// end d[1] Rectify Check
|
||||
// **********************************************
|
||||
}
|
||||
}
|
|
@ -1,39 +1,35 @@
|
|||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef REX_TASK_H
|
||||
#define REX_TASK_H
|
||||
#include "../json_support/JSON.h"
|
||||
#include "../suite/Unit.h"
|
||||
#include "../suite/Suite.h"
|
||||
#include "../config/Config.h"
|
||||
#include "../misc/helpers.h"
|
||||
#include "../lcpex/liblcpex.h"
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include "../../json/json.h"
|
||||
#include "Unit.h"
|
||||
#include "Suite.h"
|
||||
#include "Conf.h"
|
||||
#include <stdio.h>
|
||||
#include "../../Sproc/Sproc.h"
|
||||
#include "../misc/helpers.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
class Task
|
||||
{
|
||||
|
||||
protected:
|
||||
// the name of this task
|
||||
std::string name;
|
||||
|
@ -51,7 +47,10 @@ class Task
|
|||
// the readiness of this task to execute
|
||||
bool defined;
|
||||
|
||||
public:
|
||||
bool prepare_logs( std::string task_name, std::string logs_root, std::string timestamp );
|
||||
|
||||
|
||||
public:
|
||||
// constructor
|
||||
Task( int LOG_LEVEL );
|
||||
|
||||
|
@ -68,16 +67,17 @@ class Task
|
|||
std::string get_name();
|
||||
|
||||
// execute this task's definition
|
||||
void execute(Conf * configuration );
|
||||
void execute( Conf * configuration );
|
||||
|
||||
void mark_complete();
|
||||
|
||||
// returns a pointer to the dependencies vector
|
||||
std::vector<std::string> get_dependencies();
|
||||
|
||||
private:
|
||||
Logger slog;
|
||||
int LOG_LEVEL;
|
||||
|
||||
private:
|
||||
Logger slog;
|
||||
int LOG_LEVEL;
|
||||
};
|
||||
|
||||
#endif //REX_TASK_H
|
||||
#endif //REX_TASK_H
|
|
@ -0,0 +1,64 @@
|
|||
#include "shells.h"
|
||||
|
||||
class ShellException: public std::exception
|
||||
{
|
||||
public:
|
||||
explicit ShellException(const char* message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
explicit ShellException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
virtual ~ShellException() throw (){}
|
||||
|
||||
virtual const char* what() const throw (){
|
||||
return msg_.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
Shell::Shell(int LOG_LEVEL): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_shell" )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
|
||||
int Shell::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";
|
||||
|
||||
if ( loader_root.isMember("name") )
|
||||
{
|
||||
this->name = loader_root.get( "name", errmsg ).asString();
|
||||
} else {
|
||||
throw ShellException("No name attribute specified when loading a shell definition.");
|
||||
}
|
||||
|
||||
if ( loader_root.isMember("path") )
|
||||
{
|
||||
this->path = loader_root.get( "path", errmsg ).asString();
|
||||
} else {
|
||||
throw ShellException("No path attribute specified when loading a shell definition.");
|
||||
}
|
||||
|
||||
if ( loader_root.isMember("execution_arg") )
|
||||
{
|
||||
this->execution_arg = loader_root.get( "execution_arg", errmsg ).asString();
|
||||
} else {
|
||||
throw ShellException("No execution_arg attribute specified when loading a shell definition.");
|
||||
}
|
||||
|
||||
if ( loader_root.isMember("execution_arg") )
|
||||
{
|
||||
this->execution_arg = loader_root.get( "execution_arg", errmsg ).asString();
|
||||
} else {
|
||||
throw ShellException("No execution_arg attribute specified when loading a shell definition.");
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef REX_SHELLS_H
|
||||
#define REX_SHELLS_H
|
||||
|
||||
#include "../json_support/JSON.h"
|
||||
#include <string>
|
||||
|
||||
class Shell: public JSON_Loader {
|
||||
public:
|
||||
Shell( int LOG_LEVEL );
|
||||
int load_root( Json::Value loader_root );
|
||||
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string execution_arg;
|
||||
std::string source_cmd;
|
||||
|
||||
private:
|
||||
std::string shell_definitions_path;
|
||||
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif //REX_SHELLS_H
|
|
@ -1,93 +1,106 @@
|
|||
/*
|
||||
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 <string.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include "Suite.h"
|
||||
#include <dirent.h>
|
||||
|
||||
/// Suite_InvalidUnitMember - Exception thrown when a Suite tries to access a contained Unit's value that is not
|
||||
/// present in the Unit.
|
||||
#include "Suite.h"
|
||||
|
||||
/**
|
||||
* @class SuiteException
|
||||
* @brief Exception thrown when a Suite tries to access a contained Unit's value that is not present in the Unit.
|
||||
*
|
||||
* This exception is thrown when a Suite object tries to access a value in a contained Unit object that is not present in the Unit. The error message is stored in the `msg_` member variable and can be retrieved with the `what` method.
|
||||
*/
|
||||
class SuiteException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit SuiteException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for SuiteException class (C-style string).
|
||||
*
|
||||
* @param message C-style string error message. The string contents are copied upon construction. The caller is responsible for deleting the char*.
|
||||
*/
|
||||
explicit SuiteException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit SuiteException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
/**
|
||||
* @brief Constructor for SuiteException class (C++ STL string).
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit SuiteException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~SuiteException() throw (){}
|
||||
/**
|
||||
* @brief Destructor for SuiteException class.
|
||||
*
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~SuiteException() throw (){}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
/**
|
||||
* @brief Get the error message.
|
||||
*
|
||||
* @return A pointer to a const char*. The underlying memory is in the possession of the Exception object. Callers should not attempt to free the memory.
|
||||
*/
|
||||
virtual const char* what() const throw (){
|
||||
return msg_.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
protected:
|
||||
/// Error message.
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/// Suite::Suite() - Constructor for Suite class. The Suite class is simply a managed container for a Unit vector.
|
||||
/// Once instantiated, all methods will require either a JSON file or string to be loaded as deserialized Unit types
|
||||
/// before being called or will simply throw an exception.
|
||||
///
|
||||
/// From the high level, a Suite contains the full definitions of all potential Units to execute defined in the Unit
|
||||
/// definition files that it is loading. It is meant to be used in such a way that as the application iterates through
|
||||
/// the Task objects contained by the application Plan, it will iterate through the appplication's Suite, which contains
|
||||
/// the definition of all available Tasks. In this manner, defining units and executing units are split into separate
|
||||
/// human processes to allow modularly developed profiles of test suites. As inferred, Unit is expected to be one of
|
||||
/// the two types that are only instantiated once per application run, though it is designed to be used more than once
|
||||
/// if the implementor so desires.
|
||||
Suite::Suite( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_suite" )
|
||||
/**
|
||||
* @brief Constructor for Suite class.
|
||||
*
|
||||
* The Suite class is simply a managed container for a Unit vector.
|
||||
* Once instantiated, all methods will require either a JSON file or string to be loaded as deserialized Unit types
|
||||
* before being called or will simply throw an exception.
|
||||
|
||||
* From the high level, a Suite contains the full definitions of all potential Units to execute defined in the Unit
|
||||
* definition files that it is loading. It is meant to be used in such a way that as the application iterates through
|
||||
* the Task objects contained by the application Plan, it will iterate through the appplication's Suite, which contains
|
||||
* the definition of all available Tasks. In this manner, defining units and executing units are split into separate
|
||||
* human processes to allow modularly developed profiles of test suites. As inferred, Unit is expected to be one of
|
||||
* the two types that are only instantiated once per application run, though it is designed to be used more than once
|
||||
* if the implementor so desires.
|
||||
*/
|
||||
Suite::Suite( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_suite" )
|
||||
{
|
||||
this->LOG_LEVEL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the list of unit files in a directory
|
||||
*
|
||||
* This function retrieves all the files with a ".units" extension in the specified directory.
|
||||
*
|
||||
* @param[out] files The list of unit files
|
||||
* @param[in] path The path of the directory to search
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void Suite::get_units_from_dir( std::vector<std::string> * files, std::string path )
|
||||
{
|
||||
// TODO: this whole method probably needs rewritten
|
||||
DIR* dirFile = opendir( path.c_str() );
|
||||
if ( dirFile )
|
||||
{
|
||||
|
@ -127,11 +140,16 @@ void Suite::get_units_from_dir( std::vector<std::string> * files, std::string pa
|
|||
}
|
||||
|
||||
|
||||
/// Suite::load_units_file - Uses the json_root buffer on each run to append intact Units as they're
|
||||
/// deserialized from the provided file.
|
||||
///
|
||||
/// \param units_path - The file to pull the JSON-formatted units from.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
/**
|
||||
* @brief Load units from a file or directory
|
||||
*
|
||||
* This function loads units from a file or directory containing unit files.
|
||||
* The unit files must be in JSON format.
|
||||
*
|
||||
* @param[in] units_path The path to the file or directory containing unit files
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void Suite::load_units_file( std::string units_path )
|
||||
{
|
||||
std::vector<std::string> unit_files;
|
||||
|
@ -181,10 +199,14 @@ void Suite::load_units_file( std::string units_path )
|
|||
}
|
||||
|
||||
|
||||
/// Suite::get_unit - returns a contained Unit identified by name attribute.
|
||||
///
|
||||
/// \param result - the unit type receiving the unit's value
|
||||
/// \param provided_name - The name of the unit being fetched.
|
||||
/**
|
||||
* @brief Returns a contained Unit identified by the `provided_name` attribute.
|
||||
*
|
||||
* @param result The unit type that receives the unit's value.
|
||||
* @param provided_name The name of the unit being fetched.
|
||||
*
|
||||
* @throws SuiteException if the unit with the specified name is not found.
|
||||
*/
|
||||
void Suite::get_unit(Unit & result, std::string provided_name)
|
||||
{
|
||||
bool foundMatch = false;
|
||||
|
@ -203,7 +225,6 @@ void Suite::get_unit(Unit & result, std::string provided_name)
|
|||
if (! foundMatch )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Unit name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
throw SuiteException( "Undefined unit in use." );
|
||||
throw SuiteException( "Undefined unit referenced." );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef REX_SUITE_H
|
||||
#define REX_SUITE_H
|
||||
#include <vector>
|
||||
#include "../json_support/JSON.h"
|
||||
#include "../logger/Logger.h"
|
||||
#include "Unit.h"
|
||||
#include "../misc/helpers.h"
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
|
||||
/**
|
||||
* @class Suite
|
||||
* @brief Loads unit definition files and provides access to the unit definitions.
|
||||
*
|
||||
* The Suite class extends the JSON_Loader class and loads unit definition files. It stores the valid unit definitions
|
||||
* in the `units` vector. The `get_unit` method can be used to retrieve a specific unit by name. The `load_units_file`
|
||||
* method is used to load a unit definition file and add valid unit definitions to the `units` vector. The
|
||||
* `get_units_from_dir` method is used to get a list of unit definition files from a directory. The `LOG_LEVEL` and
|
||||
* `slog` member variables are used to control logging.
|
||||
*/
|
||||
class Suite: public JSON_Loader
|
||||
{
|
||||
protected:
|
||||
/// storage for the definitions we are amassing from the unit definition files
|
||||
std::vector<Unit> units;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for Suite class.
|
||||
*
|
||||
* @param LOG_LEVEL The logging level to use.
|
||||
*/
|
||||
Suite( int LOG_LEVEL );
|
||||
|
||||
/**
|
||||
* @brief Load a unit definitions file and add valid unit definitions to `units` vector.
|
||||
*
|
||||
* @param filename The path to the unit definitions file.
|
||||
*/
|
||||
void load_units_file( std::string filename );
|
||||
|
||||
/**
|
||||
* @brief Retrieve a unit by name.
|
||||
*
|
||||
* @param result The Unit object that will be set to the unit with the provided name.
|
||||
* @param provided_name The name of the unit to retrieve.
|
||||
*/
|
||||
void get_unit(Unit & result, std::string provided_name);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Get a list of unit definition files from a directory.
|
||||
*
|
||||
* @param files A pointer to a vector of strings that will be set to the list of unit definition files.
|
||||
* @param path The path to the directory containing the unit definition files.
|
||||
*/
|
||||
void get_units_from_dir( std::vector<std::string> * files, std::string path );
|
||||
|
||||
|
||||
private:
|
||||
/// The logging level to use.
|
||||
int LOG_LEVEL;
|
||||
/// A logger for logging messages.
|
||||
Logger slog;
|
||||
};
|
||||
#endif //REX_SUITE_H
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef REX_UNIT_H
|
||||
#define REX_UNIT_H
|
||||
|
||||
#include <string>
|
||||
#include "../json_support/JSON.h"
|
||||
#include "../logger/Logger.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
|
||||
/*
|
||||
* Unit is a type that represents a safely deserialized JSON object which defines what actions are taken as rex
|
||||
* iterates through it's Tasks in it's given Plan. They only define the behaviour on execution, while the tasks define
|
||||
* which Units are executed and in what order (and which Units a given Task depends on.
|
||||
*/
|
||||
class Unit: JSON_Loader
|
||||
{
|
||||
private:
|
||||
// the name of the test
|
||||
std::string name;
|
||||
|
||||
// the path of the executable this test executes when run
|
||||
std::string target;
|
||||
|
||||
// bool to indicate if the target is a shell command or not
|
||||
bool is_shell_command;
|
||||
|
||||
// the shell profile to use defined in the shell definitions file
|
||||
std::string shell_definition;
|
||||
|
||||
// bool to indicate if the target should be run in a pty
|
||||
bool force_pty;
|
||||
|
||||
// bool to indicate if the working directory should be set for this execution
|
||||
bool set_working_directory;
|
||||
|
||||
// the working directory to set for this execution
|
||||
std::string working_directory;
|
||||
|
||||
// indicator of whether the rectifier executable should be run on test failures.
|
||||
// if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required
|
||||
bool rectify;
|
||||
|
||||
// the path of the executable this test runs when the target executable fails to produce output or a 0 exit code.
|
||||
std::string rectifier;
|
||||
|
||||
// an indicator of whether the test is active or not
|
||||
// this is used as a way to give definers a way to force executors to edit arbitrary fields or prevent
|
||||
// execution of potentially dangerous or intrusive tests
|
||||
bool active;
|
||||
|
||||
// an indicator of whether or not this test is required to pass.
|
||||
// intended to be used as a flag to halt execution of further tests on failure
|
||||
bool required;
|
||||
|
||||
// an indicator of whether or not the user context should be set for this execution
|
||||
bool set_user_context;
|
||||
|
||||
// user to run process as.
|
||||
// not intended for protected accounts, handle your own security
|
||||
std::string user;
|
||||
|
||||
// group to run process as.
|
||||
// not intended for protected accounts, handle your own security
|
||||
std::string group;
|
||||
|
||||
// an indicator of whether or not the environment should be set for this execution
|
||||
// REQUIRES this->is_shell_command == true
|
||||
bool supply_environment;
|
||||
|
||||
// the path to a file containing environment variables or functions to be set for this execution
|
||||
std::string env_vars_file;
|
||||
|
||||
public:
|
||||
Unit( int LOG_LEVEL );
|
||||
|
||||
// loads a serialized jason::value object as a unit
|
||||
int load_root( Json::Value loader_root );
|
||||
|
||||
// getters
|
||||
std::string get_name();
|
||||
std::string get_target();
|
||||
bool get_is_shell_command();
|
||||
std::string get_shell_definition();
|
||||
bool get_force_pty();
|
||||
bool get_set_working_directory();
|
||||
std::string get_working_directory();
|
||||
bool get_rectify();
|
||||
std::string get_rectifier();
|
||||
bool get_active();
|
||||
bool get_required();
|
||||
bool get_set_user_context();
|
||||
std::string get_user();
|
||||
std::string get_group();
|
||||
bool get_supply_environment();
|
||||
std::string get_environment_file();
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //REX_UNIT_H
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"execution_context_override": true,
|
||||
"execution_context": "/home/bagira/development/internal/Rex/test",
|
||||
"units_path": "units/",
|
||||
"logs_path": "logs/",
|
||||
"config_version": "4"
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"plan": [
|
||||
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||
{ "name": "independent test 2", "dependencies": [ null ] },
|
||||
{ "name": "dependent test", "dependencies": [ "independent test 1" ] },
|
||||
{ "name": "curses dialog", "dependencies": [ "independent test 1" ] },
|
||||
{ "name": "fail", "dependencies": [ null ] }
|
||||
]
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "independent test 1",
|
||||
"target": "components/independent_test_1.bash --ls -s --arg",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"log": true,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false,
|
||||
"shell": "/usr/bin/bash",
|
||||
"environment": "environments/rex.variables"
|
||||
},
|
||||
{
|
||||
"name": "independent test 2",
|
||||
"target": "components/independent_test_2.bash --ls",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"log": true,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false,
|
||||
"shell": "/usr/bin/bash",
|
||||
"environment": "environments/rex.variables"
|
||||
},
|
||||
{
|
||||
"name": "dependent test",
|
||||
"target": "components/dependent_test.bash",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"log": true,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false,
|
||||
"shell": "/usr/bin/env bash",
|
||||
"environment": "environments/rex.variables"
|
||||
},
|
||||
{
|
||||
"name": "curses dialog",
|
||||
"target": "components/curses_dialog.bash",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"log": false,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false,
|
||||
"shell": "/usr/bin/env bash",
|
||||
"environment": "environments/rex.variables"
|
||||
},
|
||||
{
|
||||
"name": "fail",
|
||||
"target": "components/fail.bash",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"log": true,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false,
|
||||
"shell": "/usr/bin/env bash",
|
||||
"environment": "environments/rex.variables"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue