Compare commits
107 Commits
Examplar-1
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
879f07ec4b | |
![]() |
54d95aa582 | |
![]() |
698ba9373c | |
![]() |
d13793954d | |
![]() |
e9fd8c80bc | |
![]() |
d15847dbfe | |
![]() |
60a9f2bad2 | |
![]() |
e03a7e67b8 | |
![]() |
0561460b54 | |
![]() |
344cbfc56e | |
|
bb324087a8 | |
|
0f35e752d3 | |
|
cddc271ac7 | |
|
2904e9dcd8 | |
|
7a816d5920 | |
|
f510f2b8cc | |
|
67ab74a8c8 | |
|
d4d5a2b30d | |
|
bb466185d0 | |
|
e16c663b5f | |
|
96e7cae5df | |
|
74bce85f1e | |
|
e9556c6075 | |
![]() |
130d539f81 | |
![]() |
35248176cb | |
|
5f8ec22166 | |
|
9a33086cdd | |
|
b439959413 | |
![]() |
d0fbd30f31 | |
![]() |
004addd2b4 | |
![]() |
79bc82e365 | |
![]() |
c129f72b91 | |
![]() |
7ed6e13fa5 | |
|
bb85754dc0 | |
![]() |
5c5009972f | |
![]() |
8def175313 | |
![]() |
c16f464a3e | |
![]() |
53c1271475 | |
![]() |
119e980c20 | |
![]() |
ebdb9d643a | |
|
2871f8f8c1 | |
|
c3de36fbec | |
|
a85721a0e9 | |
|
23f64c026c | |
![]() |
5287a1baa9 | |
![]() |
9863390f67 | |
![]() |
d0391b7304 | |
![]() |
3de2dd7039 | |
![]() |
865c81b36a | |
![]() |
63c4e542ea | |
![]() |
04f9e63756 | |
![]() |
89f734de84 | |
![]() |
4b3a9170bf | |
![]() |
3d421b1ca0 | |
![]() |
199d93a2b2 | |
![]() |
dce543a15d | |
![]() |
31ed2feb7f | |
![]() |
d4a228f351 | |
![]() |
a5a729054a | |
![]() |
6db624ef93 | |
![]() |
c16d69429a | |
![]() |
a5317bfeda | |
![]() |
8b35a88643 | |
![]() |
3b467e2c0a | |
![]() |
c4828d506b | |
![]() |
c9567f20e4 | |
![]() |
4cb2449f24 | |
![]() |
58f4684449 | |
![]() |
3bb3b5f47e | |
![]() |
52734d2f2c | |
![]() |
af18419eda | |
![]() |
f4a38de0c0 | |
![]() |
3517b9cc11 | |
![]() |
137c82ebaa | |
![]() |
e49cc335f3 | |
![]() |
4ea05b842a | |
![]() |
3bc60f56de | |
![]() |
88eaea7bfa | |
![]() |
d636ece26a | |
![]() |
839c3d398a | |
![]() |
11829aca39 | |
![]() |
8f6f7761c4 | |
![]() |
722688ebf3 | |
![]() |
3591bd344d | |
![]() |
1c354b4401 | |
![]() |
0db575a075 | |
![]() |
46e56b8d6f | |
![]() |
93c22887e8 | |
![]() |
9d89f5ad6a | |
![]() |
6fca4f2d91 | |
![]() |
c75fa9d2c8 | |
![]() |
49b4e77ca8 | |
![]() |
8a95bbf219 | |
![]() |
4d66a0f059 | |
![]() |
221edee07c | |
![]() |
0c70f7ee36 | |
![]() |
2b2225f3e4 | |
![]() |
6af1082852 | |
![]() |
9d5af160c5 | |
![]() |
da50f152f2 | |
![]() |
7404f07dc5 | |
![]() |
a50293a217 | |
![]() |
660745d7d5 | |
![]() |
c37b6e1a9e | |
![]() |
50478d58ed | |
![]() |
109d227166 | |
![]() |
14d526d95c |
|
@ -2,3 +2,9 @@
|
|||
cmake-build-debug
|
||||
.idea
|
||||
./.idea
|
||||
./cmake-build-release/
|
||||
cmake-build-release
|
||||
CMakeFiles
|
||||
CMakeLists.txt
|
||||
Makefile
|
||||
sample/logs/*
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(ftests)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++1z -O0 -DDEBUG=1")
|
||||
set(SOURCE_FILES examplar.cpp src/loaders/loaders.cpp src/loaders/loaders.h src/json/jsoncpp.cpp src/loaders/JSON_Loader.cpp src/loaders/JSON_Loader.h src/loaders/helpers.cpp src/loaders/helpers.h src/loaders/Suite.cpp src/loaders/Suite.h src/loaders/Plan.cpp src/loaders/Plan.h src/loaders/Conf.cpp src/loaders/Conf.h src/loaders/Unit.cpp src/loaders/Unit.h src/loaders/Task.cpp src/loaders/Task.h src/sproc/Sproc.cpp src/sproc/Sproc.h)
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(rex)
|
||||
|
||||
add_executable(ftests ${SOURCE_FILES})
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
|
||||
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/helpers.h src/lcpex/helpers.cpp 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 )
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
# Rex
|
||||
The Meta-Automation system you didn't know you were missing. It's elegant, yet crude, it's advanced, yet simple.
|
||||
|
||||
## What is Rex?
|
||||
Rex is a project-based, json-driven execution flow/workflow tool designed to place rails around very complex automations in a controlled way that
|
||||
is easy to troubleshoot.
|
||||
|
||||
While designed for the generation of SURRO Linux, the design was kept broad for many other use cases and extensions.
|
||||
|
||||
At a high level, it is a very simple thing: It executes scripts and other executables in a predetermined order, logs
|
||||
their output, and has basic error handling using exit codes of the executables it is running.
|
||||
|
||||
Rex relies on a library of `Units` which are files that define, in json format, the executables it will execute.
|
||||
|
||||
Rex uses a `Plan` to define which of those units it will actually execute -- once selected they are called `Tasks`.
|
||||
|
||||
This allows you to have many things defined by multiple teams, and, with sufficient abstraction, use the same library of
|
||||
automations for multiple purposes throughout your environment -- bring shared library patterns in software development
|
||||
to your infrastructure operations efforts if you so desire, and more.
|
||||
|
||||
# Instructions
|
||||
These are instructions for some primitive ways of using Rex.
|
||||
|
||||
## Build
|
||||
Compiling Rex is easy. There are zero external dependencies. Build does require *cmake*.
|
||||
|
||||
~~~~
|
||||
$ cmake .
|
||||
$ make
|
||||
~~~~
|
||||
|
||||
Then place the binary where you'd like. I'd recommend packaging it for your favorite Linux distribution.
|
||||
|
||||
## High Level Usage
|
||||
|
||||
### 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
|
||||
In fact, you don't need a dual mode of automation (though it is highly recommended):
|
||||
|
||||
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.
|
||||
|
||||
## Definitions
|
||||
So you've got Rex compiled and you're ready to start automating the world.
|
||||
|
||||
If you're thinking "how do I configure this thing", this README is for you.
|
||||
|
||||
### Units
|
||||
A Unit is an automation definition, written in JSON in a UNIT FILE. Deeper into Rex’s internals, Units and Tasks have slightly different functions, but for the purposes of users, the terms can be used interchangeably. A Task is a task to be performed in a Plan, and a Unit is its definition. A Unit is a JSON object that has:
|
||||
|
||||
* A `name`, which is an identifier for the Unit used by people.
|
||||
* A `target`, which is the path to the automation script performing the work. This provides a clean linear path for huge chains of scripts to be executed in order and tracked on return for additional logic in chaining.
|
||||
* A `rectifier`, which is the path to the automation script to be executed if the target call fails.
|
||||
* An `active` attribute,which tells Rex whether or not the Unit can be used in a Plan. This gives Unit developers a way to tell Plan developers not to use the Unit.
|
||||
* A `required` attribute which tells Rex whether or not the Plan can continue if the Unit fails. If the rectify attribute is set to true, this attribute is checked after a rectifier failure. If not, this is checked after target failure. In either case, if the rectifier or target do not return successfully, Rex will halt the execution of the Plan if this is turned on for the unit being executed. Otherwise it simply moves to the next Unit being executed.
|
||||
* A `log` attribute which tells Rex whether or not to log the stdout of the task. STDERR will always be logged regardless.
|
||||
* A `user` attribute, along with its accompanying `group` attribute, which together set the identity context to execute the script as that user.
|
||||
* A `rectify` attribute, which tells Rex whether or not to execute the rectifier in the case of failure when executing the target.
|
||||
* An `environment` attribute, which points to the path of an environment file -- usually a shell script to be sourced to populate the environment executing the `target`.
|
||||
|
||||
### Tasks
|
||||
A `Task` is an action item in a `Plan`, just like in real life. In the context of Rex, a `Task` is a `Unit` that has been loaded and incorporated into a `Plan` in an actionable state. Inactive `Units` can not be loaded into a `Plan` and thus can never be a `Task`. The primary difference between a Task and a Unit is that a Unit is not actionable — it’s just a definition — while a Task is a consumable, actionable automation definition that is scheduled to execute.
|
||||
|
||||
### Suite
|
||||
A `Suite` is not visible to the user and this is only for informational purposes. A Suite is a collection of all available Unit definitions loaded from one or more UNIT FILES. Just as a Unit is the definition for a Task, a Suite is a collection of Units that define the Task components of a Plan.
|
||||
|
||||
A Suite is consumed by a Plan during the conversion of Units to Tasks, though this is not visible to the user — it just simply helps to understand the kind of abstraction taking place in the conceptual model of Rex.
|
||||
Plan
|
||||
|
||||
A Plan is the glue of all the components of Rex and is deceptively simple. A Plan loads a Suite for its Task definitions (Units), but the Tasks to actually execute are specified in the PLAN FILE. The Tasks are executed in the order specified in the PLAN FILE.
|
||||
|
||||
### FILES
|
||||
There are several types of files used by a Rex project.
|
||||
|
||||
#### CONFIG FILE and Attributes
|
||||
This is the one config file that Rex uses. The default path it looks is /etc/Rex/config.json.
|
||||
|
||||
A config file at the time of writing this specifies a single JSON object with 5 attributes:
|
||||
|
||||
* `units_path`: The `UNIT FILE` path or a path to a directory containing unit files.
|
||||
* `config_version`: The configuration VERSION.
|
||||
* `execution_context`: The current working directory to use when loading unit files, plan files, or executing Tasks.
|
||||
* `execution_context_override`: A boolean indicating whether or not the execution context should be set, or left alone. It is highly recommended to set this to `true`.
|
||||
* `logs_path`: The path relative to the execution context to store logs. This directory will be created if it does not exist.
|
||||
|
||||
#### Configuration VERSION
|
||||
The configuration version is checked to ensure that the configuration is consumable by that version of Rex. This will pave the way for reverse compatibility if the project moves in that direction.
|
||||
|
||||
#### UNIT FILE
|
||||
|
||||
The UNIT FILE is a specification of where the Units are defined. All UNIT FILES in that directory will be amalgamated to generate the Suite. These types of files must end in `*.units` for their filename.
|
||||
|
||||
#### PLAN FILE
|
||||
|
||||
The PLAN FILE is a specification of the order that Tasks are executed, and their dependencies upon each other. Dependency implementation is a touchy matter that is pending implementation, so, mileage may vary until release.
|
||||
|
||||
|
||||
|
||||
## I still don't see how this works.
|
||||
That's ok. It's in its infancy so we're always looking for ways to make it simpler. Here's a 'hello world' example.
|
||||
|
||||
### 1. Write your tests.
|
||||
First, we want to know all the things we need to be able to print "hello world" to the screen. In this case we just need to make we have the "echo" binary.
|
||||
|
||||
Write a bash script that checks if the "echo" binary is on the system.
|
||||
|
||||
#!/usr/bin/bash
|
||||
stat /usr/bin/echo
|
||||
exit $?
|
||||
|
||||
Save it as ~/check-echo.bash.
|
||||
This script will be your "target" attribute for your "hello world" unit definition.
|
||||
|
||||
### 2. Write your automation.
|
||||
Write a "hello world" script.
|
||||
|
||||
#!/usr/bin/bash
|
||||
echo "hello world"
|
||||
exit $?
|
||||
|
||||
Save it as ~/hello.bash
|
||||
This script will be your "rectify" attribute for your "hello world" unit definition.
|
||||
|
||||
### 3. Set up the Unit file.
|
||||
At this point you've got both the script that checks if hello world can run and you've got your hello world script. Time to set up the unit.
|
||||
|
||||
### 4. Add the Unit definition to the Plan.
|
||||
Next, add the unit to the plan by name.
|
||||
|
||||
### 5. Set up your config file.
|
||||
Point your config file at your plan file and your units directory.
|
||||
|
||||
### 6. Run Rex pointing at that config file.
|
||||
Execute rex:
|
||||
|
||||
```
|
||||
rex --config path/to/your/config/file.json --plan path/to/your/plan/file.json
|
||||
```
|
||||
|
||||
And you should see your 'hello world' script. Check out the `test/` directory in this repo for an example project for
|
||||
more details.
|
||||
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
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 <string>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include "src/logger/Logger.h"
|
||||
#include "src/config/Config.h"
|
||||
#include "src/suite/Suite.h"
|
||||
#include "src/plan/Plan.h"
|
||||
#include "src/misc/helpers.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()
|
||||
{
|
||||
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.");
|
||||
|
||||
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
|
||||
int verbose_flag = false;
|
||||
|
||||
// whether to show usage screen
|
||||
int help_flag = false;
|
||||
|
||||
// did the user supply an argument to config
|
||||
int config_flag = false;
|
||||
|
||||
// did the user supply an argument to plan
|
||||
int plan_flag = false;
|
||||
|
||||
// did the user ask for the version info
|
||||
int version_flag = false;
|
||||
|
||||
// default config path
|
||||
std::string config_path;
|
||||
|
||||
// default plan path
|
||||
std::string plan_path;
|
||||
|
||||
// initialise for commandline argument processing
|
||||
int c;
|
||||
int digit_optind = 0;
|
||||
|
||||
if ( argc <= 1 )
|
||||
{
|
||||
help_flag = true;
|
||||
}
|
||||
|
||||
// process commandline arguments
|
||||
while ( 1 )
|
||||
{
|
||||
int this_option_optind = optind ? optind : 1;
|
||||
int option_index = 0;
|
||||
|
||||
// commandline argument structure
|
||||
static struct option long_options[] = {
|
||||
{"verbose_flag", no_argument, 0, 'v' },
|
||||
{"version_info", no_argument, 0, 'i' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{"config", required_argument, 0, 'c' },
|
||||
{"plan", required_argument, 0, 'p' },
|
||||
{0,0,0,0}
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "vihc:p:", long_options, &option_index );
|
||||
if ( c == -1 )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
switch ( c )
|
||||
{
|
||||
case 'i':
|
||||
version_flag = true;
|
||||
case 'h':
|
||||
help_flag = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose_flag = true;
|
||||
break;
|
||||
case 'c':
|
||||
config_flag = true;
|
||||
config_path = std::string( optarg );
|
||||
break;
|
||||
case 'p':
|
||||
plan_flag = true;
|
||||
plan_path = std::string( optarg );
|
||||
break;
|
||||
case '?':
|
||||
help_flag = true;
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
help_flag = true;
|
||||
}
|
||||
|
||||
// if the user supplied no plan file, there's nothing to do but teach the user how to use this tool
|
||||
if (! plan_flag ) {
|
||||
std::cerr << "NOT SUPPLIED: PLAN_PATH" << std::endl;
|
||||
help_flag = true;
|
||||
}
|
||||
|
||||
interpolate( config_path );
|
||||
interpolate( plan_path );
|
||||
|
||||
plan_path = get_absolute_path( plan_path );
|
||||
|
||||
// default logging level
|
||||
int L_LEVEL = E_INFO;
|
||||
|
||||
// if set to verbose_flag mode, output with DEBUG level verbosity
|
||||
if ( verbose_flag )
|
||||
{
|
||||
std::cout << "Setting verbosity level to 'DBUG'..." << std::endl;
|
||||
L_LEVEL = E_DEBUG;
|
||||
}
|
||||
|
||||
// the main scope logger
|
||||
Logger slog = Logger( L_LEVEL, "_main_" );
|
||||
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.");
|
||||
|
||||
// load the paths to definitions of units.
|
||||
std::string unit_definitions_path = configuration.get_units_path();
|
||||
|
||||
// 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;
|
||||
|
||||
// initialise an empty plan
|
||||
slog.log_task( E_DEBUG, "PLAN_INIT", "Initialising Plan..." );
|
||||
Plan plan = Plan( &configuration, L_LEVEL );
|
||||
|
||||
plan.load_plan_file( plan_file );
|
||||
|
||||
|
||||
// ingest the suitable Tasks from the Suite into the Plan
|
||||
slog.log_task( E_INFO, "LOAD", "Loading planned Tasks from Suite to Plan." );
|
||||
plan.load_definitions( available_definitions );
|
||||
|
||||
slog.log_task( E_INFO, "main", "Ready to execute all actionable Tasks in Plan." );
|
||||
|
||||
try
|
||||
{
|
||||
plan.execute();
|
||||
}
|
||||
|
||||
catch ( std::exception& e)
|
||||
{
|
||||
slog.log( E_FATAL, "Caught exception.");
|
||||
slog.log( E_FATAL, e.what() );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"units_path": "/home/phanes/development/internal/Examplar/conf/units/all_test.units",
|
||||
"plan_path": "/home/phanes/development/internal/Examplar/conf/plans/test.plan",
|
||||
"config_version": "1"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"plan": [
|
||||
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||
{ "name": "independent test 2", "dependencies": [ null ] },
|
||||
{ "name": "dependent test", "dependencies": [ "independent test 1" ] }
|
||||
]
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "independent test 1",
|
||||
"target": "/usr/bin/false",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"rectify": true
|
||||
},
|
||||
{
|
||||
"name": "independent test 2",
|
||||
"target": "/usr/bin/true",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"rectify": false
|
||||
},
|
||||
{
|
||||
"name": "A DEFINITION THAT IS NOT USED",
|
||||
"target": "/usr/bin/dialog --yesno test 50 50",
|
||||
"rectifier": "/usr/bin/false",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"rectify": true
|
||||
},
|
||||
{
|
||||
"name": "dependent test",
|
||||
"target": "/usr/bin/true",
|
||||
"rectifier": "/usr/bin/false",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"rectify": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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,170 @@
|
|||
# Rex
|
||||
The Meta-Automation system you didn't know you were missing. It's elegant, yet crude, it's advanced, yet simple.
|
||||
|
||||
## What is Rex?
|
||||
Rex is a project-based, json-driven execution flow/workflow tool designed to place rails around very complex automations in a controlled way that
|
||||
is easy to troubleshoot.
|
||||
|
||||
While designed for the generation of SURRO Linux, the design was kept broad for many other use cases and extensions.
|
||||
|
||||
At a high level, it is a very simple thing: It executes scripts and other executables in a predetermined order, logs
|
||||
their output, and has basic error handling using exit codes of the executables it is running.
|
||||
|
||||
Rex relies on a library of `Units` which are files that define, in json format, the executables it will execute.
|
||||
|
||||
Rex uses a `Plan` to define which of those units it will actually execute -- once selected they are called `Tasks`.
|
||||
|
||||
This allows you to have many things defined by multiple teams, and, with sufficient abstraction, use the same library of
|
||||
automations for multiple purposes throughout your environment -- bring shared library patterns in software development
|
||||
to your infrastructure operations efforts if you so desire, and more.
|
||||
|
||||
# Instructions
|
||||
These are instructions for some primitive ways of using Rex.
|
||||
|
||||
## Build
|
||||
Compiling Rex is easy. There are zero external dependencies. Build does require *cmake*.
|
||||
|
||||
~~~~
|
||||
$ cmake .
|
||||
$ make
|
||||
~~~~
|
||||
|
||||
Then place the binary where you'd like. I'd recommend packaging it for your favorite Linux distribution.
|
||||
|
||||
## High Level Usage
|
||||
|
||||
### 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
|
||||
In fact, you don't need a dual mode of automation (though it is highly recommended):
|
||||
|
||||
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.
|
||||
|
||||
## Definitions
|
||||
So you've got Rex compiled and you're ready to start automating the world.
|
||||
|
||||
If you're thinking "how do I configure this thing", this README is for you.
|
||||
|
||||
### Units
|
||||
A Unit is an automation definition, written in JSON in a UNIT FILE. Deeper into Rex’s internals, Units and Tasks have slightly different functions, but for the purposes of users, the terms can be used interchangeably. A Task is a task to be performed in a Plan, and a Unit is its definition. A Unit is a JSON object that has:
|
||||
|
||||
* A `name`, which is an identifier for the Unit used by people.
|
||||
* A `target`, which is the path to the automation script performing the work. This provides a clean linear path for huge chains of scripts to be executed in order and tracked on return for additional logic in chaining.
|
||||
* A `rectifier`, which is the path to the automation script to be executed if the target call fails.
|
||||
* An `active` attribute,which tells Rex whether or not the Unit can be used in a Plan. This gives Unit developers a way to tell Plan developers not to use the Unit.
|
||||
* A `required` attribute which tells Rex whether or not the Plan can continue if the Unit fails. If the rectify attribute is set to true, this attribute is checked after a rectifier failure. If not, this is checked after target failure. In either case, if the rectifier or target do not return successfully, Rex will halt the execution of the Plan if this is turned on for the unit being executed. Otherwise it simply moves to the next Unit being executed.
|
||||
* A `log` attribute which tells Rex whether or not to log the stdout of the task. STDERR will always be logged regardless.
|
||||
* A `user` attribute, along with its accompanying `group` attribute, which together set the identity context to execute the script as that user.
|
||||
* A `rectify` attribute, which tells Rex whether or not to execute the rectifier in the case of failure when executing the target.
|
||||
* An `environment` attribute, which points to the path of an environment file -- usually a shell script to be sourced to populate the environment executing the `target`.
|
||||
|
||||
### Tasks
|
||||
A `Task` is an action item in a `Plan`, just like in real life. In the context of Rex, a `Task` is a `Unit` that has been loaded and incorporated into a `Plan` in an actionable state. Inactive `Units` can not be loaded into a `Plan` and thus can never be a `Task`. The primary difference between a Task and a Unit is that a Unit is not actionable — it’s just a definition — while a Task is a consumable, actionable automation definition that is scheduled to execute.
|
||||
|
||||
### Suite
|
||||
A `Suite` is not visible to the user and this is only for informational purposes. A Suite is a collection of all available Unit definitions loaded from one or more UNIT FILES. Just as a Unit is the definition for a Task, a Suite is a collection of Units that define the Task components of a Plan.
|
||||
|
||||
A Suite is consumed by a Plan during the conversion of Units to Tasks, though this is not visible to the user — it just simply helps to understand the kind of abstraction taking place in the conceptual model of Rex.
|
||||
Plan
|
||||
|
||||
A Plan is the glue of all the components of Rex and is deceptively simple. A Plan loads a Suite for its Task definitions (Units), but the Tasks to actually execute are specified in the PLAN FILE. The Tasks are executed in the order specified in the PLAN FILE.
|
||||
|
||||
### FILES
|
||||
There are several types of files used by a Rex project.
|
||||
|
||||
#### CONFIG FILE and Attributes
|
||||
This is the one config file that Rex uses. The default path it looks is /etc/Rex/config.json.
|
||||
|
||||
A config file at the time of writing this specifies a single JSON object with 5 attributes:
|
||||
|
||||
* `units_path`: The `UNIT FILE` path or a path to a directory containing unit files.
|
||||
* `config_version`: The configuration VERSION.
|
||||
* `execution_context`: The current working directory to use when loading unit files, plan files, or executing Tasks.
|
||||
* `execution_context_override`: A boolean indicating whether or not the execution context should be set, or left alone. It is highly recommended to set this to `true`.
|
||||
* `logs_path`: The path relative to the execution context to store logs. This directory will be created if it does not exist.
|
||||
|
||||
#### Configuration VERSION
|
||||
The configuration version is checked to ensure that the configuration is consumable by that version of Rex. This will pave the way for reverse compatibility if the project moves in that direction.
|
||||
|
||||
#### UNIT FILE
|
||||
|
||||
The UNIT FILE is a specification of where the Units are defined. All UNIT FILES in that directory will be amalgamated to generate the Suite. These types of files must end in `*.units` for their filename.
|
||||
|
||||
#### PLAN FILE
|
||||
|
||||
The PLAN FILE is a specification of the order that Tasks are executed, and their dependencies upon each other. Dependency implementation is a touchy matter that is pending implementation, so, mileage may vary until release.
|
||||
|
||||
|
||||
|
||||
## I still don't see how this works.
|
||||
That's ok. It's in its infancy so we're always looking for ways to make it simpler. Here's a 'hello world' example.
|
||||
|
||||
### 1. Write your tests.
|
||||
First, we want to know all the things we need to be able to print "hello world" to the screen. In this case we just need to make we have the "echo" binary.
|
||||
|
||||
Write a bash script that checks if the "echo" binary is on the system.
|
||||
|
||||
#!/usr/bin/bash
|
||||
stat /usr/bin/echo
|
||||
exit $?
|
||||
|
||||
Save it as ~/check-echo.bash.
|
||||
This script will be your "target" attribute for your "hello world" unit definition.
|
||||
|
||||
### 2. Write your automation.
|
||||
Write a "hello world" script.
|
||||
|
||||
#!/usr/bin/bash
|
||||
echo "hello world"
|
||||
exit $?
|
||||
|
||||
Save it as ~/hello.bash
|
||||
This script will be your "rectify" attribute for your "hello world" unit definition.
|
||||
|
||||
### 3. Set up the Unit file.
|
||||
At this point you've got both the script that checks if hello world can run and you've got your hello world script. Time to set up the unit.
|
||||
|
||||
### 4. Add the Unit definition to the Plan.
|
||||
Next, add the unit to the plan by name.
|
||||
|
||||
### 5. Set up your config file.
|
||||
Point your config file at your plan file and your units directory.
|
||||
|
||||
### 6. Run Rex pointing at that config file.
|
||||
Execute rex:
|
||||
|
||||
```
|
||||
rex --config path/to/your/config/file.json --plan path/to/your/plan/file.json
|
||||
```
|
||||
|
||||
And you should see your 'hello world' script. Check out the `test/` directory in this repo for an example project for
|
||||
more details.
|
||||
|
||||
|
|
@ -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,61 @@
|
|||
# Logic Tree for Rex Task Execution
|
||||
|
||||
Rex - A configuration management and workflow automation system 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/>.
|
||||
|
||||
## Key:
|
||||
-Actions are labled by indices in a zero-indexed array 'a'.
|
||||
-Decisions are labeled by indices in a zero-indexed array 'd'.
|
||||
-Decisions have two possible states, TRUE or FALSE (0 or 1 respectively).
|
||||
-Decision determinations are labeled with the decision label and a subspecifier.
|
||||
|
||||
## Diagram:
|
||||
|
||||
a[0] Execute Target
|
||||
d[0] Error Code Check
|
||||
d[0].0 ZERO
|
||||
a[1] NEXT
|
||||
d[0].1 NON-ZERO
|
||||
d[1] Rectify Check
|
||||
d[1].0 FALSE
|
||||
d[2] Required Check
|
||||
d[2].0 FALSE
|
||||
a[2] NEXT
|
||||
d[2].1 TRUE
|
||||
a[3] EXCEPTION
|
||||
d[1].1 TRUE
|
||||
a[4] Execute Rectifier
|
||||
d[3] Error Code Check
|
||||
d[3].1 NON-ZERO
|
||||
d[4] Required Check
|
||||
d[4].0 FALSE
|
||||
a[5] NEXT
|
||||
d[4].1 TRUE
|
||||
a[6] EXCEPTION
|
||||
d[3].0 ZERO
|
||||
a[7] Re-Execute Target
|
||||
d[5] Error Code Check
|
||||
d[5].0 ZERO
|
||||
a[8] NEXT
|
||||
d[5].1 NON-ZERO
|
||||
d[6] Required Check
|
||||
d[6].0 FALSE
|
||||
a[9] NEXT
|
||||
d[6].1 TRUE
|
||||
a[10] EXCEPTION
|
||||
|
54
examplar.cpp
54
examplar.cpp
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 "src/json/json.h"
|
||||
#include "src/loaders/loaders.h"
|
||||
|
||||
|
||||
int main( )
|
||||
{
|
||||
bool verbose = true;
|
||||
// A Plan is made up of Tasks, and a Suite is made up of Units.
|
||||
// A Plan declares what units are executed and a Suite declares the definitions of those units.
|
||||
Conf configuration = Conf("/home/phanes/development/internal/Examplar/conf/config.json", verbose );
|
||||
|
||||
// load the configuration file which contains filepaths to definitions of a plan and definitions of units.
|
||||
std::string definitions_file = configuration.get_units_path();
|
||||
std::string plan_file = configuration.get_plan_path();
|
||||
|
||||
Suite available_definitions;
|
||||
available_definitions.load_units_file( definitions_file, verbose );
|
||||
|
||||
Plan plan;
|
||||
plan.load_plan_file( plan_file, verbose );
|
||||
|
||||
plan.load_definitions( available_definitions, verbose );
|
||||
|
||||
std::cout << "Ready to execute all tasks in Plan." << std::endl;
|
||||
|
||||
try {
|
||||
plan.execute( verbose );
|
||||
}
|
||||
catch ( std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
echo CURSES DIALOG TEST
|
||||
echo "This is a test of how curses dialogs are handled. Expect freaky behaviour."
|
||||
|
||||
whiptail --title "Dialog title" --inputbox "Enter your name:" 0 0
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "dependent test"
|
||||
echo This test depends on another test having succeeded in order to execute.
|
||||
echo This tests dependencies.
|
||||
exit $?
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
echo "Failure handling test."
|
||||
echo "This test will fail on purpose."
|
||||
>&2 echo "This test is printing to stderr."
|
||||
exit 1
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/bash
|
||||
#
|
||||
echo test from script
|
||||
#/usr/bin/dialog --title "This should be one argument" --inputbox "Enter your name:" 0 0
|
||||
|
||||
|
||||
|
||||
env
|
|
@ -0,0 +1,3 @@
|
|||
echo "independent test 2 output"
|
||||
echo "independent test says TEST_VAR is ${TEST_VAR}"
|
||||
exit $?
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/bash
|
||||
|
||||
set -a
|
||||
|
||||
echo "variables file says hello and set a variable named TEST_VAR"
|
||||
TEST_VAR="999"
|
||||
TERM=xterm
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"plan": [
|
||||
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||
{ "name": "independent test 2", "dependencies": [ null ] }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"plan": [
|
||||
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||
{ "name": "independent test 2", "dependencies": [ null ] }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"config": {
|
||||
"project_root": "$HOME/development/internal/rex/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,40 @@
|
|||
{
|
||||
"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": "$USER",
|
||||
"group": "$USER",
|
||||
"supply_environment": true,
|
||||
"environment": "environments/rex.variables"
|
||||
},
|
||||
{
|
||||
"name": "independent test 2",
|
||||
"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": "$USER",
|
||||
"group": "$USER",
|
||||
"supply_environment": true,
|
||||
"environment": "environments/rex.variables"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
#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));
|
||||
}
|
||||
|
||||
void removeTrailingSlash(std::string &str) {
|
||||
if (!str.empty() && str.back() == '/') {
|
||||
str.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
removeTrailingSlash(relative_path);
|
||||
return this->project_root + "/" + relative_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 + "' (" + tmp_S.path + ")" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
interpolate( filename );
|
||||
this->slog.log_task( E_DEBUG, "LOAD", "Loading configuration file: " + 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 );
|
||||
interpolate( this->project_root );
|
||||
|
||||
// convert to an absolute path after all the interpolation is done.
|
||||
this->project_root = get_absolute_path( this->project_root );
|
||||
|
||||
set_object_s( "logs_path", this->logs_path, filename );
|
||||
interpolate( this->logs_path );
|
||||
|
||||
// all other paths are relative to project_root
|
||||
set_object_s_derivedpath( "units_path", this->units_path, filename );
|
||||
interpolate( this->units_path );
|
||||
|
||||
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename );
|
||||
interpolate( this->shell_definitions_path );
|
||||
|
||||
// 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,15 @@
|
|||
#include "helpers.h"
|
||||
|
||||
ssize_t write_all(int fd, const void *buf, size_t count) {
|
||||
const char *p = (const char *)buf;
|
||||
while (count > 0) {
|
||||
ssize_t written = write(fd, p, count);
|
||||
if (written == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN) continue; // Retry
|
||||
return -1; // Other errors
|
||||
}
|
||||
count -= written;
|
||||
p += written;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef LCPEX_HELPERS_H
|
||||
#define LCPEX_HELPERS_H
|
||||
|
||||
#include <unistd.h>
|
||||
#include "errno.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
|
||||
|
||||
ssize_t write_all(int fd, const void *buf, size_t count);
|
||||
|
||||
#endif //LCPEX_HELPERS_H
|
|
@ -0,0 +1,353 @@
|
|||
#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 << "LAUNCHER: " << prefix << std::endl;
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
int lcpex(
|
||||
std::string command,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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_fh, stderr_log_fh, context_override, context_user, context_group, supply_environment );
|
||||
}
|
||||
|
||||
// otherwise, we will use the execute function
|
||||
return execute( command, stdout_log_fh, stderr_log_fh, 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 << "REX: Aborting: context user not found: " << context_user << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
|
||||
std::cerr << "REX: Aborting: context group not found: " << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
|
||||
std::cerr << "REX: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
|
||||
std::cerr << "REX: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
|
||||
exit(1);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "REX: 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,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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 ) {
|
||||
// this breaks reuse of env variables in between executions
|
||||
//clearenv();
|
||||
}
|
||||
|
||||
// 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);
|
||||
// after the poll() call, add a check to see if both pipes are closed
|
||||
if (!(watched_fds[CHILD_PIPE_NAMES::STDOUT_READ].events & POLLIN) &&
|
||||
!(watched_fds[CHILD_PIPE_NAMES::STDERR_READ].events & POLLIN)) {
|
||||
break_out = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (errno == EAGAIN) { continue; } else {
|
||||
// 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_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write_all(STDOUT_FILENO, buf, byte_count);
|
||||
} else if (this_fd == CHILD_PIPE_NAMES::STDERR_READ) {
|
||||
// the child's stderr pipe is readable
|
||||
write_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write_all(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
|
||||
// don't close the file descriptor yet, there might still be data to read
|
||||
// instead, remove the POLLIN event to avoid getting a POLLHUP event in the next poll() call
|
||||
watched_fds[this_fd].events &= ~POLLIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
// Drain the pipes before exiting
|
||||
while ((byte_count = read(fd_child_stdout_pipe[READ_END], buf, BUFFER_SIZE)) > 0) {
|
||||
write_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write_all(STDOUT_FILENO, buf, byte_count);
|
||||
}
|
||||
while ((byte_count = read(fd_child_stderr_pipe[READ_END], buf, BUFFER_SIZE)) > 0) {
|
||||
write_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write_all(STDERR_FILENO, buf, byte_count);
|
||||
}
|
||||
|
||||
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,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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,26 @@
|
|||
#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,309 @@
|
|||
#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,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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 );
|
||||
|
||||
if ( stdout_log_fh == NULL ) {
|
||||
safe_perror( "Error opening STDOUT log file. Aborting.", &ttyOrig );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
if ( stderr_log_fh == NULL ) {
|
||||
safe_perror( "Error opening STDERR log file. Aborting.", &ttyOrig );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
// after the poll() call, add a check to see if both pipes are closed
|
||||
if (!(watched_fds[1].events & POLLIN) &&
|
||||
!(watched_fds[2].events & POLLIN)) {
|
||||
break_out = true;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (errno == EAGAIN) { continue; } else {
|
||||
// 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_all(masterFd, buf, byte_count);
|
||||
} else if (this_fd == 1 ) {
|
||||
// child pty sent some stuff, write to parent stdout and log
|
||||
write_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write_all(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_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write_all(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;
|
||||
// }
|
||||
if (watched_fds[this_fd].revents & POLLHUP) {
|
||||
// this pipe has hung up
|
||||
// don't close the file descriptor yet, there might still be data to read
|
||||
// instead, remove the POLLIN event to avoid getting a POLLHUP event in the next poll() call
|
||||
watched_fds[this_fd].events &= ~POLLIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for child to exit, capture status
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
while ((byte_count = read(watched_fds[1].fd, buf, BUFFER_SIZE)) > 0) {
|
||||
write_all(stdout_log_fh->_fileno, buf, byte_count);
|
||||
write_all(STDOUT_FILENO, buf, byte_count);
|
||||
}
|
||||
|
||||
while ((byte_count = read(fd_child_stderr_pipe[READ_END], buf, BUFFER_SIZE)) > 0) {
|
||||
write_all(stderr_log_fh->_fileno, buf, byte_count);
|
||||
write_all(STDERR_FILENO, buf, byte_count);
|
||||
}
|
||||
|
||||
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,
|
||||
FILE * stdout_log_fh,
|
||||
FILE * stderr_log_fh,
|
||||
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,54 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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"
|
||||
|
||||
/// CONF_PLANPATH_INVALID - Exception thrown when the Conf type can not load the supplied path for the Plan definition
|
||||
/// file.
|
||||
class CONF_PLANPATH_INVALID: public std::runtime_error { public:
|
||||
CONF_PLANPATH_INVALID(): std::runtime_error("conf: The supplied path for the plan definition file is invalid.") {}
|
||||
};
|
||||
|
||||
/// CONF_UNITSPATH_INVALID - Exception thrown when the Conf type can not load the supplied path for the Unit definition
|
||||
/// files.
|
||||
class CONF_UNITSPATH_INVALID: public std::runtime_error { public:
|
||||
CONF_UNITSPATH_INVALID(): std::runtime_error("conf: The supplied path for the unit definition file is invalid.") {}
|
||||
};
|
||||
|
||||
/// 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, bool verbose ): JSON_Loader()
|
||||
{
|
||||
// load the conf file.
|
||||
this->load_json_file( filename, verbose );
|
||||
|
||||
// find the path to the plan file
|
||||
if (this->get_serialized(this->plan_path, "plan_path", true) != 0 ) { throw CONF_PLANPATH_INVALID(); }
|
||||
|
||||
// find the path to the unit definitions file
|
||||
if (this->get_serialized(this->units_path, "units_path", true) != 0 ) { throw CONF_UNITSPATH_INVALID(); }
|
||||
};
|
||||
|
||||
/// Conf::get_plan_path - Retrieves the path to the Plan definition file from the application configuration file.
|
||||
std::string Conf::get_plan_path() { return this->plan_path.asString(); }
|
||||
|
||||
/// 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.asString(); }
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_CONF_H
|
||||
#define FTESTS_CONF_H
|
||||
#include "JSON_Loader.h"
|
||||
|
||||
class Conf: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
Json::Value plan_path;
|
||||
Json::Value units_path;
|
||||
|
||||
public:
|
||||
Conf( std::string filename, bool verbose );
|
||||
std::string get_plan_path();
|
||||
std::string get_units_path();
|
||||
};
|
||||
|
||||
#endif //FTESTS_CONF_H
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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"
|
||||
#include "helpers.h"
|
||||
#include <stdexcept>
|
||||
|
||||
/// 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()
|
||||
{
|
||||
this->populated = false;
|
||||
}
|
||||
|
||||
/// 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, bool verbose )
|
||||
{
|
||||
// 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 ) )
|
||||
{
|
||||
std::cerr << "File '" << filename << "' does not exist." << std::endl;
|
||||
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 )
|
||||
{
|
||||
std::cerr << "Failed to parse '" << filename << "':\n\t" << json_reader.getFormattedErrorMessages();
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
// if in verbose mode, give the user an "it worked" message
|
||||
if (verbose)
|
||||
{
|
||||
std::cout << "Parsed '" << filename << "' with " << this->json_root.size() << " element(s)." << std::endl;
|
||||
}
|
||||
}
|
||||
// Flag as ready for consumption.
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
/// 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, bool verbose )
|
||||
{
|
||||
// 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 )
|
||||
{
|
||||
std::cerr << "Failed to parse adhoc JSON value." << std::endl << input << std::endl << std::endl << json_reader.getFormattedErrorMessages();
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
// if in verbose mode, give the user an "it worked" message
|
||||
if ( verbose )
|
||||
{
|
||||
std::cout << "Successfully parsed JSON string with " << this->json_root.size() << " elements. Value:" << std::endl;
|
||||
std::cout << input << std::endl << std::endl;
|
||||
}
|
||||
}
|
||||
// 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, bool verbose)
|
||||
{
|
||||
// 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
|
||||
if ( verbose )
|
||||
{
|
||||
// verbose mode tells the user what key we were looking for.
|
||||
std::cerr << "Failed to find key '" << key << "'." << std::endl;
|
||||
}
|
||||
// exit code for failure
|
||||
return 1;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_JLOADER_H
|
||||
#define FTESTS_JLOADER_H
|
||||
#include "../json/json.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
class JSON_Loader
|
||||
{
|
||||
protected:
|
||||
Json::Value json_root;
|
||||
bool populated;
|
||||
|
||||
public:
|
||||
// constructor
|
||||
JSON_Loader();
|
||||
|
||||
// load from json file
|
||||
void load_json_file( std::string filename, bool verbose );
|
||||
|
||||
// load from std::string json
|
||||
void load_json_string( std::string input, bool verbose );
|
||||
|
||||
// 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, bool verbose);
|
||||
};
|
||||
#endif //FTESTS_JLOADER_H
|
|
@ -1,273 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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(): JSON_Loader() {};
|
||||
|
||||
/// 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, bool verbose)
|
||||
{
|
||||
// plan always loads from file
|
||||
this->load_json_file( filename, verbose );
|
||||
|
||||
// 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", verbose ) == 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;
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
tmp_T.load_root( this->json_root[ index ], verbose );
|
||||
this->tasks.push_back( tmp_T );
|
||||
if ( verbose ) {
|
||||
std::cout << "Added task \"" << tmp_T.get_name() << "\" to Plan." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, bool verbose )
|
||||
{
|
||||
// placeholder Unit
|
||||
Unit tmp_U;
|
||||
|
||||
// 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, verbose );
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 )
|
||||
{
|
||||
std::cerr << "Task name \"" << provided_name << "\" was referenced but not defined!" << std::endl;
|
||||
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;
|
||||
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;
|
||||
// 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( bool verbose )
|
||||
{
|
||||
// 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()) )
|
||||
{
|
||||
if ( verbose )
|
||||
{
|
||||
std::cout << "Executing task \"" << this->tasks[i].get_name() << "\"." << std::endl;
|
||||
}
|
||||
try {
|
||||
this->tasks[i].execute( verbose );
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
throw Plan_Task_GeneralExecutionException( "Plan Task: \"" + this->tasks[i].get_name() + "\" reported: " + e.what() );
|
||||
}
|
||||
} else {
|
||||
// not all deps met for this task
|
||||
throw Plan_Task_Missing_Dependency( "Plan Task \"" + this->tasks[i].get_name() + "\" was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_PLAN_H
|
||||
#define FTESTS_PLAN_H
|
||||
|
||||
#include <string>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.h"
|
||||
#include "Task.h"
|
||||
|
||||
class Plan: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
// storage for the tasks that make up the plan
|
||||
std::vector<Task> tasks;
|
||||
|
||||
public:
|
||||
Plan();
|
||||
|
||||
// append this->tasks from JSON file
|
||||
void load_plan_file( std::string filename, bool verbose );
|
||||
|
||||
// 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, bool verbose );
|
||||
|
||||
// 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 verbose );
|
||||
|
||||
bool all_dependencies_complete(std::string name);
|
||||
};
|
||||
|
||||
#endif //FTESTS_PLAN_H
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 "Suite.h"
|
||||
|
||||
/// Suite_InvalidUnitMember - Exception thrown when a Suite tries to access a contained Unit's value that is not
|
||||
/// present in the Unit.
|
||||
class Suite_InvalidUnitMember: public std::runtime_error { public:
|
||||
// TODO rework this to accept the name of the member not able to be fetched.
|
||||
Suite_InvalidUnitMember(): std::runtime_error("Suite: Attempted to access a member of a Unit that is not set.") {}
|
||||
};
|
||||
|
||||
|
||||
/// 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(): JSON_Loader() {};
|
||||
|
||||
/// 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 filename - The file to pull the JSON-formatted units from.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Suite::load_units_file( std::string filename, bool verbose )
|
||||
{
|
||||
// will use json_root buffer on each run to append to this->units vector as valid units are found.
|
||||
this->load_json_file( filename, verbose );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer with a json::value object in the supplied filename
|
||||
if ( this->get_serialized( jbuff, "units", verbose ) == 0)
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->units vector
|
||||
// buffer for units to append:
|
||||
Unit tmp_U;
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
// assemble the unit from json_root using the built-in value operator
|
||||
tmp_U.load_root( this->json_root[ index ] );
|
||||
if ( tmp_U.get_active() ) {
|
||||
// append to this->units
|
||||
this->units.push_back( tmp_U );
|
||||
if ( verbose ) {
|
||||
std::cout << "Added unit \"" << tmp_U.get_name() << "\" to Suite." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
void Suite::get_unit(Unit & result, std::string provided_name)
|
||||
{
|
||||
bool foundMatch = false;
|
||||
|
||||
for ( int i = 0; i < this->units.size(); i++ )
|
||||
{
|
||||
std::string unit_name = this->units[i].get_name();
|
||||
if ( unit_name == provided_name )
|
||||
{
|
||||
result = this->units[i];
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! foundMatch )
|
||||
{
|
||||
std::cerr << "Unit name \"" << provided_name << "\" was referenced but not defined!" << std::endl;
|
||||
throw Suite_InvalidUnitMember();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_UNITS_H
|
||||
#define FTESTS_UNITS_H
|
||||
|
||||
#include <vector>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.h"
|
||||
#include "Unit.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, empty
|
||||
Suite();
|
||||
|
||||
// load a unit definitions file and add valid unit definitions to this->units
|
||||
void load_units_file( std::string filename, bool verbose );
|
||||
|
||||
// returns the unit identified by name
|
||||
void get_unit(Unit & result, std::string provided_name);
|
||||
};
|
||||
|
||||
#endif //FTESTS_UNITS_H
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include "../sproc/Sproc.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 access a unit of a Task that is not defined.") {}
|
||||
};
|
||||
|
||||
/// Task_RequiredButFailedTask - Exception thrown when a Task is failed but required, and rectification also failed.
|
||||
class Task_RequiredButFailedTask: public std::runtime_error {
|
||||
public:
|
||||
Task_RequiredButFailedTask(): std::runtime_error("Task: Attempted to execute a Task that failed and was required.") {}
|
||||
};
|
||||
|
||||
/// Task_RequiredButFailedTask - Exception thrown when a Task is failed but required, and rectification also failed but
|
||||
/// returned with a zero exit code (dont try to fool the check).
|
||||
class Task_RequiredButRectifierDoesNotHeal: public std::runtime_error {
|
||||
public:
|
||||
Task_RequiredButRectifierDoesNotHeal(): std::runtime_error("Task: The rectification script was executed and reported success, but did not actually heal the faulty condition of the Task target.") {}
|
||||
};
|
||||
|
||||
/// 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()
|
||||
{
|
||||
// it hasn't executed yet.
|
||||
this->complete = false;
|
||||
|
||||
// it hasn't been matched with a definition yet.
|
||||
this->defined = false;
|
||||
}
|
||||
|
||||
/// 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, bool verbose )
|
||||
{
|
||||
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() );
|
||||
if ( verbose ) {
|
||||
std::cout << "Added dependency \"" << des_dep_root[i].asString()
|
||||
<< "\" to task \"" << this->get_name() << "\"." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, bool verbose )
|
||||
{
|
||||
this->definition = selected_unit;
|
||||
if ( verbose ) {
|
||||
std::cout << "Loaded definition \"" << selected_unit.get_name() << "\" for task \""
|
||||
<< this->get_name() << "\"." << std::endl;
|
||||
}
|
||||
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( bool verbose )
|
||||
{
|
||||
// DUFFING - If Examplar is broken it's probably going to be in this block.
|
||||
|
||||
// PREWORK
|
||||
// throw if unit not coupled to all necessary values since Task is stateful (yes, stateful is okay)
|
||||
if (! this->has_definition() ) {
|
||||
throw Task_NotReady();
|
||||
}
|
||||
|
||||
// get the name
|
||||
std::string task_name = this->definition.get_name();
|
||||
// END PREWORK
|
||||
|
||||
|
||||
// get the target execution command
|
||||
std::string target_command = this->definition.get_target();
|
||||
|
||||
// if we're in verbose mode, do some verbose things
|
||||
if ( verbose ) {
|
||||
std::cout << "\tUsing unit \"" << task_name << "\"." << std::endl;
|
||||
std::cout << "\tExecuting target \"" << target_command << "\"." << std::endl;
|
||||
}
|
||||
|
||||
// execute target
|
||||
int return_code = Sproc::execute( target_command );
|
||||
|
||||
// d[0] check exit code of target
|
||||
if (return_code == 0) {
|
||||
// Zero d[0] return from target execution, good to return
|
||||
if ( verbose ) {
|
||||
std::cout << "\tTarget " << task_name << " succeeded." << std::endl;
|
||||
}
|
||||
this->mark_complete();
|
||||
// next
|
||||
} else {
|
||||
// Non-Zero d[0] from initial target execution, get to d[1]
|
||||
std::cout << "\tTarget \"" << task_name << "\" failed with exit code " << return_code << "." << std::endl;
|
||||
|
||||
// check if rectify pattern is enabled d[1]
|
||||
if ( this->definition.get_rectify() ) {
|
||||
// yes d[1]
|
||||
std::cout << "\tRectification pattern is enabled for \"" << task_name << "\"." << std::endl;
|
||||
// execute RECTIFIER
|
||||
std::string rectifier_command = this->definition.get_rectifier();
|
||||
std::cout << "\tExecuting rectification: " << rectifier_command << "." << std::endl;
|
||||
int rectifier_error = Sproc::execute( rectifier_command );
|
||||
|
||||
// d[3] check exit code of rectifier
|
||||
if (rectifier_error) {
|
||||
//d[3] non-zero
|
||||
|
||||
std::cout << "\tRectification of \"" << task_name << "\" failed with exit code "
|
||||
<< rectifier_error << "." << std::endl;
|
||||
|
||||
// d[2] check if REQUIRED
|
||||
if ( this->definition.get_required() ) {
|
||||
// d[2] yes
|
||||
// halt/exception
|
||||
throw Task_RequiredButFailedTask();
|
||||
}
|
||||
// d[2] no
|
||||
// next
|
||||
}
|
||||
// d[3] zero
|
||||
|
||||
// execute target
|
||||
std::cout << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl;
|
||||
int retry_code = Sproc::execute( target_command );
|
||||
|
||||
// d[4] exit code of target retry
|
||||
if (retry_code == 0) {
|
||||
// d[4] zero
|
||||
}
|
||||
// d[4] non-zero
|
||||
// d[5] required check
|
||||
if ( this->definition.get_required() ) {
|
||||
// d[5] yes
|
||||
std::cout << "\tTask \"" << task_name << "\" is required but rectification did not heal." << std::endl;
|
||||
throw Task_RequiredButRectifierDoesNotHeal();
|
||||
}
|
||||
// d[5] no
|
||||
// next
|
||||
}
|
||||
// no d[1]
|
||||
std::cout << "\tRectification is not enabled for \"" << task_name << "\"." << std::endl;
|
||||
// required d[2]
|
||||
if ( this->definition.get_required() ) {
|
||||
// d[2] yes
|
||||
// This is executing.....
|
||||
std::cout << "\tThis task is required to continue the plan." << std::endl;
|
||||
// but these are NOT executing?????
|
||||
throw Task_RequiredButFailedTask();
|
||||
} // d[2] no
|
||||
std::cout << "\tThis task is not required to continue the plan." << std::endl;
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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>
|
||||
|
||||
/// Unit_NotPopulated - Meant to be thrown when a Unit type is not populated before being used.
|
||||
/// Signaled by use of the 'populated' boolean member of the Unit class.
|
||||
class Unit_NotPopulated: public std::runtime_error { public:
|
||||
Unit_NotPopulated(): std::runtime_error("Unit: Attempted to access a member before loading values.") {}
|
||||
};
|
||||
|
||||
/// Unit_DataStructureException - Meant to be thrown when a Unit type is accessing a member that does not exist.
|
||||
class Unit_DataStructureException: public std::runtime_error { public:
|
||||
// TODO rework this to accept the key name being fetched
|
||||
Unit_DataStructureException(): std::runtime_error("Unit: Attempted to access a member not present in defined Unit.") {}
|
||||
};
|
||||
|
||||
/// 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() {}
|
||||
|
||||
/// 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 Unit_DataStructureException();
|
||||
|
||||
if ( loader_root.isMember("target") )
|
||||
{ this->target = loader_root.get("target", errmsg).asString(); } else throw Unit_DataStructureException();
|
||||
|
||||
if ( loader_root.isMember("rectifier") )
|
||||
{ this->rectifier = loader_root.get("rectifier", errmsg).asString(); } else throw Unit_DataStructureException();
|
||||
|
||||
if ( loader_root.isMember("active") )
|
||||
{ this->active = loader_root.get("active", errmsg).asBool(); } else throw Unit_DataStructureException();
|
||||
|
||||
if ( loader_root.isMember("required") )
|
||||
{ this->required = loader_root.get("required", errmsg).asBool(); } else throw Unit_DataStructureException();
|
||||
|
||||
if ( loader_root.isMember("rectify") )
|
||||
{ this->rectify = loader_root.get("rectify", errmsg).asBool(); } else throw Unit_DataStructureException();
|
||||
|
||||
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, true );
|
||||
|
||||
// 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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
return this->rectify;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 Examplar
|
||||
* 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 FTESTS_UNIT_H
|
||||
#define FTESTS_UNIT_H
|
||||
#include <string>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.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;
|
||||
|
||||
public:
|
||||
Unit();
|
||||
|
||||
// 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();
|
||||
bool get_active();
|
||||
bool get_required();
|
||||
bool get_rectify();
|
||||
};
|
||||
|
||||
#endif //FTESTS_UNIT_H
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_HELPERS_H
|
||||
#define FTESTS_HELPERS_H
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
bool exists (const std::string& name);
|
||||
|
||||
|
||||
|
||||
#endif //FTESTS_HELPERS_H
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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,28 +0,0 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_LOADERS_H
|
||||
#define FTESTS_LOADERS_H
|
||||
|
||||
#include "JSON_Loader.h"
|
||||
#include "Suite.h"
|
||||
#include "Plan.h"
|
||||
#include "Conf.h"
|
||||
|
||||
#endif //FTESTS_LOADERS_H
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
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 "Logger.h"
|
||||
|
||||
Logger::Logger( int LOG_LEVEL, std::string mask )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
this->mask = mask;
|
||||
}
|
||||
|
||||
void Logger::log( int LOG_LEVEL, std::string msg )
|
||||
{
|
||||
std::string ERR = "XXXX";
|
||||
|
||||
if ( LOG_LEVEL <= this->LOG_LEVEL )
|
||||
{
|
||||
switch ( LOG_LEVEL )
|
||||
{
|
||||
case E_DEBUG: ERR = "DBUG"; break;
|
||||
case E_FATAL: ERR = "FATL"; break;
|
||||
case E_INFO: ERR = "INFO"; break;
|
||||
case E_WARN: ERR = "WARN"; break;
|
||||
}
|
||||
|
||||
std::string s_msg = "[" + ERR + "] " + msg;
|
||||
|
||||
if ( LOG_LEVEL == E_FATAL | LOG_LEVEL == E_WARN )
|
||||
{
|
||||
std::cerr << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl;
|
||||
} else {
|
||||
std::cout << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
Rex - A configuration management and workflow automation tool that
|
||||
compiles and runs in minimal environments.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
© 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
|
||||
|
@ -18,18 +19,33 @@
|
|||
|
||||
*/
|
||||
|
||||
#ifndef FTESTS_SPROC_H
|
||||
#define FTESTS_SPROC_H
|
||||
#ifndef REX_LOGGER_H
|
||||
#define REX_LOGGER_H
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
||||
// should be able to recieve path of binary to be executed as well as any parameters
|
||||
class Sproc {
|
||||
public:
|
||||
// call the object. returnvalue is enum representing external execution attempt not binary exit code
|
||||
static int execute( std::string input );
|
||||
enum L_LVL {
|
||||
E_FATAL,
|
||||
E_WARN,
|
||||
E_INFO,
|
||||
E_DEBUG
|
||||
};
|
||||
|
||||
#endif //FTESTS_SPROC_H
|
||||
class Logger {
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //REX_LOGGER_H
|
|
@ -0,0 +1,152 @@
|
|||
#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}` or `$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 env1( "\\$\\{([^}]+)\\}" );
|
||||
// static std::regex env2( "\\$([^/]+)" ); // matches $VAR_NAME until a / is found
|
||||
// std::smatch match;
|
||||
// while ( std::regex_search( text, match, env1 ) || std::regex_search( text, match, env2 ) )
|
||||
// {
|
||||
// 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 );
|
||||
// }
|
||||
//}
|
||||
void interpolate( std::string & text )
|
||||
{
|
||||
std::regex env1( "\\$\\{([^}]+)\\}" );
|
||||
std::regex env2( "\\$([^/]+)" ); // matches $VAR_NAME until a / is found
|
||||
std::smatch match;
|
||||
while ( std::regex_search( text, match, env1 ) || std::regex_search( text, match, env2 ) )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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_HELPERS_H
|
||||
#define REX_HELPERS_H
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <syslog.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
|
||||
|
||||
bool exists (const std::string& name);
|
||||
|
||||
std::string get_working_path();
|
||||
|
||||
bool is_file( std::string );
|
||||
|
||||
bool is_dir( std::string );
|
||||
|
||||
// expand environment variables in string
|
||||
void interpolate( std::string & text);
|
||||
|
||||
std::string get_8601();
|
||||
|
||||
const char * command2args( std::string input_string );
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
#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,639 @@
|
|||
#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 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();
|
||||
|
||||
interpolate(task_name);
|
||||
this->slog.log_task( E_DEBUG, task_name, "Using unit definition: \"" + task_name + "\"." );
|
||||
|
||||
interpolate(command);
|
||||
interpolate(shell_name);
|
||||
interpolate(user);
|
||||
interpolate(group);
|
||||
interpolate(environment_file);
|
||||
interpolate(new_working_dir);
|
||||
interpolate(rectifier);
|
||||
interpolate(logs_root);
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// set these first so the pre-execution logs get there.
|
||||
/*
|
||||
* create the logs dir here
|
||||
*/
|
||||
|
||||
if (! this->prepare_logs( task_name, logs_root ) )
|
||||
{
|
||||
throw TaskException("Could not prepare logs for task execution at '" + logs_root + "'.");
|
||||
}
|
||||
|
||||
std::string timestamp = get_8601();
|
||||
std::string stdout_log_file = logs_root + "/" + task_name + "/" + timestamp + ".stdout.log";
|
||||
std::string stderr_log_file = logs_root + "/" + task_name + "/" + timestamp + ".stderr.log";
|
||||
|
||||
// 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+" );
|
||||
|
||||
// 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_fh,
|
||||
stderr_log_fh,
|
||||
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_fh,
|
||||
stderr_log_fh,
|
||||
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_fh,
|
||||
stderr_log_fh,
|
||||
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
|
||||
// **********************************************
|
||||
}
|
||||
// close the log file handles
|
||||
fclose(stdout_log_fh);
|
||||
fclose(stderr_log_fh);
|
||||
}
|
|
@ -1,29 +1,32 @@
|
|||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
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 FTESTS_TASK_H
|
||||
#define FTESTS_TASK_H
|
||||
#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 "../json/json.h"
|
||||
#include "Unit.h"
|
||||
#include "Suite.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
class Task
|
||||
{
|
||||
|
@ -44,15 +47,18 @@ class Task
|
|||
// the readiness of this task to execute
|
||||
bool defined;
|
||||
|
||||
public:
|
||||
bool prepare_logs( std::string task_name, std::string logs_root );
|
||||
|
||||
|
||||
public:
|
||||
// constructor
|
||||
Task();
|
||||
Task( int LOG_LEVEL );
|
||||
|
||||
// load a json::value into task members (second stage deserialization)
|
||||
void load_root( Json::Value loader_root, bool verbose );
|
||||
void load_root( Json::Value loader_root );
|
||||
|
||||
// appends definition unit as child member
|
||||
void load_definition( Unit definition, bool verbose );
|
||||
void load_definition( Unit definition );
|
||||
|
||||
bool is_complete();
|
||||
bool has_definition();
|
||||
|
@ -61,12 +67,17 @@ class Task
|
|||
std::string get_name();
|
||||
|
||||
// execute this task's definition
|
||||
void execute( bool verbose );
|
||||
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;
|
||||
};
|
||||
|
||||
#endif //FTESTS_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("source_cmd") )
|
||||
{
|
||||
this->source_cmd = loader_root.get( "source_cmd", errmsg ).asString();
|
||||
} else {
|
||||
throw ShellException("No source_cmd 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,15 +0,0 @@
|
|||
#include "Sproc.h"
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <wait.h>
|
||||
|
||||
/// 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 input) {
|
||||
int child_exit_code = -666;
|
||||
child_exit_code = system( input.c_str() );
|
||||
child_exit_code = WEXITSTATUS( child_exit_code );
|
||||
return child_exit_code;
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
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 "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:
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructor for SuiteException class (C++ STL string).
|
||||
*
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit SuiteException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Destructor for SuiteException class.
|
||||
*
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~SuiteException() throw (){}
|
||||
|
||||
/**
|
||||
* @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_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 )
|
||||
{
|
||||
std::string entry;
|
||||
|
||||
struct dirent* hFile;
|
||||
errno = 0;
|
||||
while (( hFile = readdir( dirFile )) != NULL )
|
||||
{
|
||||
if ( !strcmp( hFile->d_name, "." ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ( !strcmp( hFile->d_name, ".." ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hidden files
|
||||
if ( hFile->d_name[0] == '.' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// dirFile.name is the name of the file. Do whatever string comparison
|
||||
// you want here. Something like:
|
||||
if ( strstr( hFile->d_name, ".units" ))
|
||||
{
|
||||
std::string full_path = path + "/" + hFile->d_name;
|
||||
files->push_back( full_path );
|
||||
}
|
||||
}
|
||||
closedir( dirFile );
|
||||
} else {
|
||||
this->slog.log( E_DEBUG, "File not found: " + path );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
if ( is_dir( units_path ) )
|
||||
{
|
||||
// we have a directory path. find all files ending in *.units and load them into a vector<std::string>
|
||||
get_units_from_dir( &unit_files, units_path );
|
||||
}
|
||||
|
||||
if ( is_file( units_path ) )
|
||||
{
|
||||
unit_files.push_back( units_path );
|
||||
}
|
||||
|
||||
this->slog.log( E_INFO, "Unit files found: " + std::to_string( unit_files.size() ) );
|
||||
|
||||
for ( int i = 0; i < unit_files.size(); i++ )
|
||||
{
|
||||
// will use json_root buffer on each run to append to this->units vector as valid units are found.
|
||||
this->load_json_file( unit_files[i] );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer with a json::value object in the supplied units_path
|
||||
if ( this->get_serialized( jbuff, "units" ) == 0)
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->units vector
|
||||
// buffer for units to append:
|
||||
Unit tmp_U = Unit( this->LOG_LEVEL );
|
||||
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
// assemble the unit from json_root using the built-in value operator
|
||||
tmp_U.load_root( this->json_root[ index ] );
|
||||
if ( tmp_U.get_active() ) {
|
||||
// append to this->units
|
||||
this->units.push_back( tmp_U );
|
||||
this->slog.log( E_INFO, "Added unit \"" + tmp_U.get_name() + "\" to Suite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
for ( int i = 0; i < this->units.size(); i++ )
|
||||
{
|
||||
std::string unit_name = this->units[i].get_name();
|
||||
if ( unit_name == provided_name )
|
||||
{
|
||||
result = this->units[i];
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! foundMatch )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Unit name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
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
|
Loading…
Reference in New Issue