Compare commits
24 Commits
Examplar-1
...
Rex-1.0a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
865c81b36a | ||
|
|
63c4e542ea | ||
|
|
04f9e63756 | ||
|
|
89f734de84 | ||
|
|
4b3a9170bf | ||
|
|
3d421b1ca0 | ||
|
|
199d93a2b2 | ||
|
|
dce543a15d | ||
|
|
31ed2feb7f | ||
|
|
d4a228f351 | ||
|
|
a5a729054a | ||
|
|
6db624ef93 | ||
|
|
c16d69429a | ||
|
|
a5317bfeda | ||
|
|
8b35a88643 | ||
|
|
3b467e2c0a | ||
|
|
c4828d506b | ||
|
|
c9567f20e4 | ||
|
|
4cb2449f24 | ||
|
|
58f4684449 | ||
|
|
3bb3b5f47e | ||
|
|
52734d2f2c | ||
|
|
af18419eda | ||
|
|
f4a38de0c0 |
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(examplar)
|
project(rex)
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++1z -O0 -DDEBUG=1")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++1z -O0 -DDEBUG=1")
|
||||||
set(SOURCE_FILES examplar.cpp src/loaders/abstract/loaders.cpp src/loaders/abstract/loaders.h src/json/jsoncpp.cpp src/loaders/low_level/JSON_Loader.cpp src/loaders/low_level/JSON_Loader.h src/loaders/misc/helpers.cpp src/loaders/misc/helpers.h src/loaders/abstract/Suite.cpp src/loaders/abstract/Suite.h src/loaders/abstract/Plan.cpp src/loaders/abstract/Plan.h src/loaders/abstract/Conf.cpp src/loaders/abstract/Conf.h src/loaders/abstract/Unit.cpp src/loaders/abstract/Unit.h src/loaders/abstract/Task.cpp src/loaders/abstract/Task.h src/Sproc/Sproc.cpp src/Sproc/Sproc.h src/Logger/Logger.cpp src/Logger/Logger.h)
|
set(SOURCE_FILES Rex.cpp src/loaders/abstract/loaders.cpp src/loaders/abstract/loaders.h src/json/jsoncpp.cpp src/loaders/low_level/JSON_Loader.cpp src/loaders/low_level/JSON_Loader.h src/loaders/misc/helpers.cpp src/loaders/misc/helpers.h src/loaders/abstract/Suite.cpp src/loaders/abstract/Suite.h src/loaders/abstract/Plan.cpp src/loaders/abstract/Plan.h src/loaders/abstract/Conf.cpp src/loaders/abstract/Conf.h src/loaders/abstract/Unit.cpp src/loaders/abstract/Unit.h src/loaders/abstract/Task.cpp src/loaders/abstract/Task.h src/Sproc/Sproc.cpp src/Sproc/Sproc.h src/Logger/Logger.cpp src/Logger/Logger.h)
|
||||||
|
|
||||||
add_executable(examplar ${SOURCE_FILES})
|
add_executable(rex ${SOURCE_FILES})
|
||||||
|
|||||||
115
README.md
115
README.md
@@ -1,9 +1,28 @@
|
|||||||
|
# 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
|
# Instructions
|
||||||
These are instructions for using Examplar.
|
These are instructions for some primitive ways of using Rex.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
Compiling Rex is easy. There are zero external dependencies. Build does require *cmake*.
|
||||||
Compiling Examplar is easy. There are zero external dependencies. Build does require *cmake*.
|
|
||||||
|
|
||||||
~~~~
|
~~~~
|
||||||
$ cmake .
|
$ cmake .
|
||||||
@@ -14,59 +33,85 @@ Then place the binary where you'd like. I'd recommend packaging it for your fav
|
|||||||
|
|
||||||
## High Level Usage
|
## High Level Usage
|
||||||
|
|
||||||
|
### Self-Healing Workflows
|
||||||
|
|
||||||
|
Rex introduces self-healing workflows.
|
||||||
|
|
||||||
|
#### Model A
|
||||||
1. Write a script that does a thing.
|
1. Write a script that does a thing.
|
||||||
2. Write a script that checks if that thing is done.
|
2. Write a script that checks if that thing is done.
|
||||||
3. Set up your check as a target in a unit.
|
3. Set up your check script as a `target` in a `unit`.
|
||||||
4. Set up your script as its rectifier.
|
4. Set up your script that does the thing as that unit's `rectifier`.
|
||||||
5. Turn on the rectify pattern in the unit definition.
|
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
|
## Definitions
|
||||||
So you've got Examplar compiled and you're ready to start automating the world.
|
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 article is for you.
|
If you're thinking "how do I configure this thing", this README is for you.
|
||||||
|
|
||||||
### Units
|
### 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 Unit is an automation definition, written in JSON in a UNIT FILE. Deeper into Examplar’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 `name`, which is an identifier for the Unit used by people.
|
* A `rectifier`, which is the path to the automation script to be executed if the target call fails.
|
||||||
* 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.
|
* 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 `rectifier`, which is the path to the automation script to be executed if the target call fails.
|
* 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 `rectify` attribute, which tells Examplar whether or not to execute the rectifier in the case of failure when executing the target.
|
* A `log` attribute which tells Rex whether or not to log the stdout of the task. STDERR will always be logged regardless.
|
||||||
* An `active` attribute,which tells Examplar 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 `user` attribute, along with its accompanying `group` attribute, which together set the identity context to execute the script as that user.
|
||||||
* A `required` attribute which tells Examplar 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, Examplar 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 `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
|
### 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.
|
||||||
|
|
||||||
A Task is an action item in a Plan, just like in real life. In the context of Examplar, 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 a consumable, actionable automation.
|
### Suite
|
||||||
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 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.
|
||||||
|
|
||||||
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 Examplar.
|
|
||||||
Plan
|
Plan
|
||||||
|
|
||||||
A Plan is the glue of all the components of Examplar 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.
|
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
|
### FILES
|
||||||
There are several files used by Examplar.
|
There are several types of files used by a Rex project.
|
||||||
|
|
||||||
#### CONFIG FILE and Attributes
|
#### CONFIG FILE and Attributes
|
||||||
|
This is the one config file that Rex uses. The default path it looks is /etc/Rex/config.json.
|
||||||
This is the one config file that Examplar uses. The default path it looks is /etc/Examplar/config.json.
|
|
||||||
|
|
||||||
A config file at the time of writing this specifies a single JSON object with 5 attributes:
|
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.
|
* `units_path`: The `UNIT FILE` path or a path to a directory containing unit files.
|
||||||
* `plan_path`: The PLAN FILE path. There is only ever one plan executed in a single run.
|
|
||||||
* `config_version`: The configuration VERSION.
|
* `config_version`: The configuration VERSION.
|
||||||
* `execution_context`: The current working directory to use when loading unit files, plan files, or executing Tasks.
|
* `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`.
|
* `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
|
#### 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.
|
||||||
The configuration version is checked to ensure that the configuration is consumable by that version of Examplar. This will pave the way for reverse compatibility if the project moves in that direction.
|
|
||||||
|
|
||||||
|
|
||||||
#### UNIT FILE
|
#### UNIT FILE
|
||||||
|
|
||||||
@@ -79,7 +124,6 @@ The PLAN FILE is a specification of the order that Tasks are executed, and their
|
|||||||
|
|
||||||
|
|
||||||
## I still don't see how this works.
|
## 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.
|
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.
|
### 1. Write your tests.
|
||||||
@@ -113,11 +157,14 @@ Next, add the unit to the plan by name.
|
|||||||
### 5. Set up your config file.
|
### 5. Set up your config file.
|
||||||
Point your config file at your plan file and your units directory.
|
Point your config file at your plan file and your units directory.
|
||||||
|
|
||||||
### 6. Run Examplar pointing at that config file.
|
### 6. Run Rex pointing at that config file.
|
||||||
Execute examplar:
|
Execute rex:
|
||||||
|
|
||||||
examplar --verbose --config path/to/your/config/file.json
|
```
|
||||||
|
rex --config path/to/your/config/file.json --plan path/to/your/plan/file.json
|
||||||
|
```
|
||||||
|
|
||||||
And you should see your 'hello world' script.
|
And you should see your 'hello world' script. Check out the `test/` directory in this repo for an example project for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
228
Rex.cpp
Normal file
228
Rex.cpp
Normal file
@@ -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 <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
#include "src/loaders/abstract/loaders.h"
|
||||||
|
#include "src/Logger/Logger.h"
|
||||||
|
|
||||||
|
void version_info()
|
||||||
|
{
|
||||||
|
std::cout << "pre-release alpha" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_usage()
|
||||||
|
{
|
||||||
|
// commandline switches:
|
||||||
|
// -h help OPTIONAL
|
||||||
|
// -v verbose OPTIONAL
|
||||||
|
// -c CONFIG_FILE REQUIRED
|
||||||
|
// -p PLAN_FILE REQUIRED
|
||||||
|
|
||||||
|
fprintf( stderr, "\nUsage:\n\trex [ -h | --help ] [ -v | --verbose ] ( ( ( -c | --config ) CONFIG_PATH ) ( -p | plan ) PLAN_PATH ) )\n" );
|
||||||
|
|
||||||
|
fprintf( stderr, "\nOptional Arguments:\n");
|
||||||
|
fprintf( stderr, "\t-h | --help\n\t\tThis usage screen. Mutually exclusive to all other options.\n");
|
||||||
|
fprintf( stderr, "\t-v | --verbose\n\t\tSets verbose output. Generally more than you want to see.\n");
|
||||||
|
fprintf( stderr, "\nRequired Arguments:\n");
|
||||||
|
fprintf( stderr, "\t-c | --config\n\t\tSupply the directory path for the configuration file.\n");
|
||||||
|
fprintf( stderr, "\t-p | --plan\n\t\tSupply the directory path for the plan file to execute.\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user wants the help screen, just show it and leave
|
||||||
|
if ( (help_flag) | (! config_flag) | (! plan_flag) )
|
||||||
|
{
|
||||||
|
print_usage();
|
||||||
|
exit( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// default logging level
|
||||||
|
int L_LEVEL = E_INFO;
|
||||||
|
|
||||||
|
// 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( E_INFO, "* Initialising Logging...");
|
||||||
|
|
||||||
|
// configuration object that reads from config_path
|
||||||
|
Conf configuration = Conf( config_path, L_LEVEL );
|
||||||
|
|
||||||
|
// check if context override is set in the config file
|
||||||
|
if ( configuration.has_context_override() )
|
||||||
|
{
|
||||||
|
// if so, set the CWD.
|
||||||
|
chdir( configuration.get_execution_context().c_str() );
|
||||||
|
slog.log( E_DEBUG, "* Setting execution context: " + get_working_path() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Rex Paradigm:
|
||||||
|
// - A Suite is made up of Units.
|
||||||
|
// - A Unit is a definition of an executable along with various options surrounding its context and behaviour.
|
||||||
|
// - A Plan is made up of Tasks.
|
||||||
|
// - A Unit becomes a Task when it is added to a Plan.
|
||||||
|
|
||||||
|
// A Plan contains what units are executed and a Suite contains the definitions of those units.
|
||||||
|
std::string plan_file = plan_path;
|
||||||
|
|
||||||
|
// load the filepaths to definitions of a plan and definitions of units.
|
||||||
|
std::string definitions_file = configuration.get_units_path();
|
||||||
|
|
||||||
|
// initialise an empty suite (unit definitions library)
|
||||||
|
slog.log( E_DEBUG, "* Initialising Suite...");
|
||||||
|
Suite available_definitions = Suite( L_LEVEL );
|
||||||
|
|
||||||
|
// load units into suite
|
||||||
|
slog.log( E_INFO, "* Loading all actionable Units into Suite..." );
|
||||||
|
available_definitions.load_units_file( definitions_file );
|
||||||
|
|
||||||
|
// initialise an empty plan
|
||||||
|
slog.log( E_DEBUG, "* Initialising Plan..." );
|
||||||
|
Plan plan = Plan( &configuration, L_LEVEL );
|
||||||
|
|
||||||
|
// load the plan the user supplied
|
||||||
|
slog.log( E_INFO, "* Loading Plan...");
|
||||||
|
plan.load_plan_file( plan_file );
|
||||||
|
|
||||||
|
// ingest the suitable Tasks from the Suite into the Plan
|
||||||
|
slog.log( E_INFO, "* Loading planned Tasks from Suite to Plan." );
|
||||||
|
plan.load_definitions( available_definitions );
|
||||||
|
|
||||||
|
slog.log( E_INFO, "* 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;
|
||||||
|
}
|
||||||
170
docs/README.md
Normal file
170
docs/README.md
Normal file
@@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,8 +1,9 @@
|
|||||||
# Logic Tree for Examplar Task Execution
|
# Logic Tree for Rex Task Execution
|
||||||
|
|
||||||
Examplar - An automation and testing framework.
|
Rex - A configuration management and workflow automation system 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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
162
examplar.cpp
162
examplar.cpp
@@ -1,162 +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 <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
#include "src/json/json.h"
|
|
||||||
#include "src/loaders/abstract/loaders.h"
|
|
||||||
#include "src/Logger/Logger.h"
|
|
||||||
#include "src/loaders/misc/helpers.h"
|
|
||||||
|
|
||||||
void print_usage()
|
|
||||||
{
|
|
||||||
printf("examplar [ -h | --help ] [ -v | --verbose ] [ -e | --execution-context EXECUTION_CONTEXT ][ -c | --config CONFIG_PATH ]\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main( int argc, char * argv[] )
|
|
||||||
{
|
|
||||||
int opt;
|
|
||||||
bool verbose = false;
|
|
||||||
bool show_help = false;
|
|
||||||
|
|
||||||
// indicator of whether examplar should use a commandline argument for overriding the context
|
|
||||||
// instead of what's supplied in the test file
|
|
||||||
bool cli_context_supplied = false;
|
|
||||||
|
|
||||||
std::string config_path = "/etc/Examplar/config.json";
|
|
||||||
std::string execution_context;
|
|
||||||
|
|
||||||
// commandline switches:
|
|
||||||
// -h help
|
|
||||||
// -v verbose
|
|
||||||
// -c CONFIG_FILE_PATH -- defaults to '/etc/Examplar/config.json'
|
|
||||||
// -e EXECUTION_CONTEXT -- current working directory when executing unit targets
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
static struct option long_options[] =
|
|
||||||
{
|
|
||||||
{"verbose", no_argument, 0, 'v'},
|
|
||||||
{"help", no_argument, 0, 'h'},
|
|
||||||
{"config", required_argument, 0, 'c'},
|
|
||||||
{"execution-context", required_argument, 0, 'e'},
|
|
||||||
{0, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
int option_index = 0;
|
|
||||||
|
|
||||||
opt = getopt_long( argc, argv, "vhec:", long_options, &option_index );
|
|
||||||
|
|
||||||
if ( opt == -1 )
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch ( opt )
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
if ( long_options[option_index].flag !=0 )
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
show_help = true;
|
|
||||||
case 'v':
|
|
||||||
verbose = true;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
config_path = std::string( optarg );
|
|
||||||
break;
|
|
||||||
case '?':
|
|
||||||
print_usage();
|
|
||||||
exit( 1 );
|
|
||||||
case 'e':
|
|
||||||
cli_context_supplied = true;
|
|
||||||
execution_context = std::string( optarg );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( show_help )
|
|
||||||
{
|
|
||||||
print_usage();
|
|
||||||
exit( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
int L_LEVEL = E_INFO;
|
|
||||||
if ( verbose )
|
|
||||||
{
|
|
||||||
L_LEVEL = E_DEBUG;
|
|
||||||
std::cout << "Verbosity is DBUG." << std::endl;
|
|
||||||
} else {
|
|
||||||
L_LEVEL = E_INFO;
|
|
||||||
std::cout << "Verbosity is INFO." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger slog = Logger( L_LEVEL, "examplar" );
|
|
||||||
|
|
||||||
// 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(config_path, L_LEVEL );
|
|
||||||
|
|
||||||
// check if context override
|
|
||||||
|
|
||||||
if ( configuration.has_context_override() )
|
|
||||||
{
|
|
||||||
// if so, set the CWD.
|
|
||||||
chdir( configuration.get_execution_context().c_str() );
|
|
||||||
slog.log( E_DEBUG, "Set execution context: " + get_working_path() );
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the user set this option as a commandline argument
|
|
||||||
if ( cli_context_supplied )
|
|
||||||
{
|
|
||||||
// override the test file's specified execution context
|
|
||||||
configuration.set_execution_context( execution_context );
|
|
||||||
slog.log( E_DEBUG, "Set execution context from commandline: " + execution_context );
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the 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 = Suite( L_LEVEL );
|
|
||||||
available_definitions.load_units_file( definitions_file );
|
|
||||||
|
|
||||||
Plan plan = Plan( &configuration, L_LEVEL );
|
|
||||||
plan.load_plan_file( plan_file );
|
|
||||||
|
|
||||||
plan.load_definitions( available_definitions );
|
|
||||||
|
|
||||||
slog.log( E_DEBUG, "Ready to execute all tasks in Plan." );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
plan.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
catch ( std::exception& e)
|
|
||||||
{
|
|
||||||
slog.log( E_FATAL, e.what() );
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,23 @@
|
|||||||
//
|
/*
|
||||||
// Created by bagira on 6/13/20.
|
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"
|
#include "Logger.h"
|
||||||
|
|
||||||
Logger::Logger( int LOG_LEVEL, std::string mask )
|
Logger::Logger( int LOG_LEVEL, std::string mask )
|
||||||
@@ -10,7 +26,7 @@ Logger::Logger( int LOG_LEVEL, std::string mask )
|
|||||||
this->mask = mask;
|
this->mask = mask;
|
||||||
|
|
||||||
setlogmask( LOG_UPTO( this->LOG_LEVEL ) );
|
setlogmask( LOG_UPTO( this->LOG_LEVEL ) );
|
||||||
openlog( this->mask.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_PERROR | LOG_LOCAL1 );
|
openlog( "rex", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_PERROR | LOG_LOCAL1 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,19 +49,10 @@ void Logger::log( int LOG_LEVEL, std::string msg )
|
|||||||
|
|
||||||
if ( LOG_LEVEL == E_FATAL | LOG_LEVEL == E_WARN )
|
if ( LOG_LEVEL == E_FATAL | LOG_LEVEL == E_WARN )
|
||||||
{
|
{
|
||||||
std::cerr << "[" << this->get_8601() << "]\t[" << this->mask << "]\t[" << ERR << "]\t" << msg.c_str() << std::endl;
|
std::cerr << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl;
|
||||||
} else {
|
} else {
|
||||||
std::cout << "[" << this->get_8601() << "]\t[" << this->mask << "]\t[" << ERR << "]\t" << msg.c_str() << std::endl;
|
std::cout << "[" << get_8601() << "] [" << ERR << "] " << "[" << this->mask << "] " << msg.c_str() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Logger::get_8601()
|
|
||||||
{
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
auto itt = std::chrono::system_clock::to_time_t(now);
|
|
||||||
std::ostringstream ss;
|
|
||||||
// ss << std::put_time(gmtime(&itt), "%FT%TZ");
|
|
||||||
ss << std::put_time(localtime(&itt), "%Y-%m-%d_%H:%M:%S");
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,33 @@
|
|||||||
//
|
/*
|
||||||
// Created by bagira on 6/13/20.
|
Rex - A configuration management and workflow automation tool that
|
||||||
//
|
compiles and runs in minimal environments.
|
||||||
|
|
||||||
#ifndef EXAMPLAR_LOGGER_H
|
© SILO GROUP and Chris Punches, 2020.
|
||||||
#define EXAMPLAR_LOGGER_H
|
|
||||||
|
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_LOGGER_H
|
||||||
|
#define REX_LOGGER_H
|
||||||
|
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <chrono>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include "../loaders/misc/helpers.h"
|
||||||
|
|
||||||
enum L_LVL {
|
enum L_LVL {
|
||||||
E_FATAL,
|
E_FATAL,
|
||||||
@@ -27,9 +44,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
std::string mask;
|
std::string mask;
|
||||||
std::string get_8601();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //EXAMPLAR_LOGGER_H
|
#endif //REX_LOGGER_H
|
||||||
|
|||||||
@@ -1,15 +1,414 @@
|
|||||||
|
/*
|
||||||
|
Rex - A configuration management and workflow automation tool that
|
||||||
|
compiles and runs in minimal environments.
|
||||||
|
|
||||||
|
© SILO GROUP and Chris Punches, 2020.
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
#include "Sproc.h"
|
#include "Sproc.h"
|
||||||
#include <unistd.h>
|
#include "../loaders/misc/helpers.h"
|
||||||
#include <cstring>
|
#include "sys/stat.h"
|
||||||
#include <wait.h>
|
|
||||||
|
#define PARENT default
|
||||||
|
|
||||||
|
enum PIPE_FILE_DESCRIPTORS {
|
||||||
|
READ_END = 0,
|
||||||
|
WRITE_END = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum READ_RESULTS {
|
||||||
|
READ_EOF = 0,
|
||||||
|
READ_PIPEOPEN_O_NONBLOCK = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FORK_STATES {
|
||||||
|
FORK_FAILURE = -1,
|
||||||
|
CHILD = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------
|
||||||
|
* HELPERS
|
||||||
|
* ------------------ */
|
||||||
|
|
||||||
|
// converts username to UID
|
||||||
|
// returns false on failure
|
||||||
|
int username_to_uid( std::string username, int & uid )
|
||||||
|
{
|
||||||
|
// assume failure unless proven otherwise
|
||||||
|
int r_code = false;
|
||||||
|
|
||||||
|
struct passwd * pw;
|
||||||
|
if ( ( pw = getpwnam( username.c_str() ) ) != NULL )
|
||||||
|
{
|
||||||
|
// successful user lookup
|
||||||
|
r_code = true;
|
||||||
|
uid = pw->pw_uid;
|
||||||
|
} else {
|
||||||
|
// failed lookup, do nothing, assumed failure
|
||||||
|
}
|
||||||
|
return r_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
// converts group name to GID
|
||||||
|
// returns false on failure
|
||||||
|
int groupname_to_gid( std::string groupname, int & gid )
|
||||||
|
{
|
||||||
|
int r_code = false;
|
||||||
|
|
||||||
|
struct group * gp;
|
||||||
|
if ( ( gp = getgrnam( groupname.c_str() ) ) != NULL )
|
||||||
|
{
|
||||||
|
r_code = true;
|
||||||
|
gid = gp->gr_gid;
|
||||||
|
} else {
|
||||||
|
// failed lookup, do nothing, assumed failure
|
||||||
|
}
|
||||||
|
return r_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// teebuf constructor
|
||||||
|
teebuf::teebuf(std::streambuf *sb1, std::streambuf *sb2): sb1(sb1), sb2(sb2)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// teebuf overflow method
|
||||||
|
int teebuf::overflow( int c )
|
||||||
|
{
|
||||||
|
if (c == EOF)
|
||||||
|
{
|
||||||
|
return !EOF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int const r1 = sb1->sputc(c);
|
||||||
|
int const r2 = sb2->sputc(c);
|
||||||
|
return r1 == EOF || r2 == EOF ? EOF : c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// teebuf sync method
|
||||||
|
int teebuf::sync()
|
||||||
|
{
|
||||||
|
int const r1 = sb1->pubsync();
|
||||||
|
int const r2 = sb2->pubsync();
|
||||||
|
return r1 == 0 && r2 == 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// teestream impl constructor
|
||||||
|
teestream::teestream(std::ostream &o1, std::ostream &o2) : std::ostream( &tbuf), tbuf( o1.rdbuf(), o2.rdbuf() )
|
||||||
|
{}
|
||||||
|
|
||||||
|
// SET PROCESS TO A CERTAIN IDENTITY CONTEXT
|
||||||
|
int set_identity_context( std::string task_name, std::string user_name, std::string group_name, Logger slog ) {
|
||||||
|
// the UID and GID for the username and groupname provided for context setting
|
||||||
|
int context_user_id;
|
||||||
|
int context_group_id;
|
||||||
|
|
||||||
|
// show the user something usable in debug mode
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as user '" + user_name + "'.");
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Attempt: Running as group_name '" + group_name + "'.");
|
||||||
|
|
||||||
|
// convert username to UID
|
||||||
|
if ( username_to_uid(user_name, context_user_id ) )
|
||||||
|
{
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] UID of '" + user_name + "' is '" + std::to_string(context_user_id ) + "'." );
|
||||||
|
} else {
|
||||||
|
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up UID for '" + user_name + "'.");
|
||||||
|
return SPROC_RETURN_CODES::UID_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert group name to GID
|
||||||
|
if ( groupname_to_gid(group_name, context_group_id ) )
|
||||||
|
{
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] GID of '" + group_name + "' is '" + std::to_string(context_group_id ) + "'." );
|
||||||
|
} else {
|
||||||
|
slog.log( E_FATAL, "[ '" + task_name + "' ] Failed to look up GID for '" + group_name + "'.");
|
||||||
|
return SPROC_RETURN_CODES::GID_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setgid(context_group_id) == 0) {
|
||||||
|
slog.log(E_DEBUG,
|
||||||
|
"[ '" + task_name + "' ] Successfully set GID to '" + std::to_string(context_group_id) + "' (" +
|
||||||
|
group_name + ").");
|
||||||
|
} else {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set GID. Panicking.");
|
||||||
|
return SPROC_RETURN_CODES::SET_GID_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setuid(context_user_id) == 0) {
|
||||||
|
slog.log(E_DEBUG,
|
||||||
|
"[ '" + task_name + "' ] Successfully set UID to '" + std::to_string(context_user_id) + "' (" +
|
||||||
|
user_name + ").");
|
||||||
|
} else {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Failed to set UID. Panicking.");
|
||||||
|
return SPROC_RETURN_CODES::SET_UID_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SPROC_RETURN_CODES::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sproc::execute
|
/// Sproc::execute
|
||||||
///
|
///
|
||||||
/// \param input - The commandline input to execute.
|
/// \param input - The commandline input to execute.
|
||||||
/// \return - The return code of the execution of input in the calling shell.
|
/// \return - The return code of the execution of input in the calling shell.
|
||||||
int Sproc::execute(std::string input) {
|
int Sproc::execute(std::string shell, std::string environment_file, std::string user_name, std::string group_name, std::string command, int LOG_LEVEL, std::string task_name, bool log_to_file, std::string logs_dir )
|
||||||
int child_exit_code = -666;
|
{
|
||||||
child_exit_code = system( input.c_str() );
|
// the logger
|
||||||
child_exit_code = WEXITSTATUS( child_exit_code );
|
Logger slog = Logger( LOG_LEVEL, "_sproc" );
|
||||||
return child_exit_code;
|
|
||||||
}
|
// if you get this return value, it's an issue with this method and not your
|
||||||
|
// called executable.
|
||||||
|
int exit_code_raw = SPROC_RETURN_CODES::EXEC_FAILURE_GENERAL;
|
||||||
|
|
||||||
|
// An explanation is due here:
|
||||||
|
// We want to log the STDOUT and STDERR of the child process, while still displaying them in the parent, in a way
|
||||||
|
// that does not interfere with, for example libcurses compatibility.
|
||||||
|
|
||||||
|
// To simplify the handling of I/O, we will "tee" STDOUT and STDERR of the parent to respective log files.
|
||||||
|
|
||||||
|
// Then fork(), and exec() the command to execute in the child, and link its STDOUT/STDERR to the parents' in
|
||||||
|
// realtime.
|
||||||
|
|
||||||
|
// Since the parent has a Tee between STDOUT/STDOUT_LOG and another between STDERR/STDERR_LOG, simply piping the
|
||||||
|
// child STDOUT/STDERR to the parent STDOUT/STDERR should simplify I/O redirection happening here without
|
||||||
|
// potentially corrupting user interaction with TUIs in the processes. This should give us our log and our output
|
||||||
|
// in as hands off a way as possible with as few assumptions as possible, while still doing this in a somewhat C++-y
|
||||||
|
// way.
|
||||||
|
if (! is_dir( logs_dir ) ) {
|
||||||
|
int check = mkdir( logs_dir.c_str(), 0777 );
|
||||||
|
if (! check ) {
|
||||||
|
slog.log( E_FATAL, "Sprocket couldn't create the logs parent directory." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string timestamp = get_8601();
|
||||||
|
|
||||||
|
std::string contained_dir = logs_dir + "/" + task_name;
|
||||||
|
if (! is_dir( contained_dir ) ) {
|
||||||
|
int check = mkdir( contained_dir.c_str(), 0777 );
|
||||||
|
if (! check ) {
|
||||||
|
slog.log( E_FATAL, "Sprocket couldn't create the instance log directory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set up the "Tee" with the parent
|
||||||
|
std::string child_stdout_log_path = contained_dir + "/" + timestamp + ".stdout.log";
|
||||||
|
std::string child_stderr_log_path = contained_dir + "/" + timestamp + ".stderr.log";
|
||||||
|
|
||||||
|
std::ofstream stdout_log;
|
||||||
|
std::ofstream stderr_log;
|
||||||
|
|
||||||
|
stdout_log.open( child_stdout_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||||
|
stderr_log.open( child_stderr_log_path.c_str(), std::ofstream::out | std::ofstream::app );
|
||||||
|
|
||||||
|
// avoid cyclic dependencies between stdout and tee_out
|
||||||
|
std::ostream tmp_stdout( std::cout.rdbuf() );
|
||||||
|
std::ostream tmp_stderr( std::cerr.rdbuf() );
|
||||||
|
|
||||||
|
// writing to this ostream derivative will write to stdout log file and std::cout
|
||||||
|
teestream tee_out(tmp_stdout, stdout_log);
|
||||||
|
teestream tee_err(tmp_stderr, stderr_log);
|
||||||
|
|
||||||
|
// pop the cout/cerr buffers to the appropriate Tees' buffers
|
||||||
|
|
||||||
|
// These cause a segfault when used with the I/O redirection happening around fork, pipe, dup2, execl...
|
||||||
|
//std::cout.rdbuf( tee_out.rdbuf() );
|
||||||
|
//std::cerr.rdbuf( tee_err.rdbuf() );
|
||||||
|
// ....and I don't know why.
|
||||||
|
|
||||||
|
// build the command to execute in the shell
|
||||||
|
std::string sourcer = ". " + environment_file + " && " + command;
|
||||||
|
// Show the user a debug print of what is going to be executed in the shell.
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] Shell call for loading: ``" + sourcer + "``.");
|
||||||
|
|
||||||
|
// file descriptors for parent/child i/o
|
||||||
|
int child_stdout_pipe[2];
|
||||||
|
int child_stderr_pipe[2];
|
||||||
|
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] STDIN/STDOUT/STDERR file descriptors created." );
|
||||||
|
|
||||||
|
// man 3 pipe
|
||||||
|
if (pipe(child_stdout_pipe) == -1 ) {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] STDOUT PIPE FAILED");
|
||||||
|
return SPROC_RETURN_CODES::PIPE_FAILED;
|
||||||
|
} else {
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] file descriptors piped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// man 3 pipe
|
||||||
|
if (pipe(child_stderr_pipe) == -1 ) {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] STDERR PIPE FAILED");
|
||||||
|
return SPROC_RETURN_CODES::PIPE_FAILED;
|
||||||
|
} else {
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] file descriptors piped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// fork a process
|
||||||
|
pid_t pid = fork();
|
||||||
|
slog.log( E_DEBUG, "[ '" + task_name + "' ] Process forked. Reporting. (PID: " + std::to_string(pid) + ")" );
|
||||||
|
|
||||||
|
switch ( pid ) {
|
||||||
|
case FORK_STATES::FORK_FAILURE:
|
||||||
|
{
|
||||||
|
// fork failed
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Fork Failed.");
|
||||||
|
exit( FORK_FAILED );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FORK_STATES::CHILD:
|
||||||
|
{
|
||||||
|
// enter child process
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] Entering child process.");
|
||||||
|
|
||||||
|
while ((dup2(child_stdout_pipe[WRITE_END], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
|
||||||
|
while ((dup2(child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
|
||||||
|
|
||||||
|
close( child_stdout_pipe[WRITE_END] );
|
||||||
|
close( child_stdout_pipe[READ_END] );
|
||||||
|
|
||||||
|
close( child_stderr_pipe[WRITE_END] );
|
||||||
|
close( child_stderr_pipe[READ_END] );
|
||||||
|
|
||||||
|
slog.log(E_INFO, "[ '" + task_name + "' ] TEE Logging enabled.");
|
||||||
|
slog.log(E_DEBUG, "[ '" + task_name + "' ] DUP2: child_*_pipe[1]->STD*_FILENO");
|
||||||
|
|
||||||
|
// set identity context
|
||||||
|
// set gid and uid
|
||||||
|
int context_status = set_identity_context(task_name, user_name, group_name, slog);
|
||||||
|
if (!(context_status)) {
|
||||||
|
slog.log(E_FATAL, "[ '" + task_name + "' ] Identity context set failed.");
|
||||||
|
return context_status;
|
||||||
|
} else {
|
||||||
|
slog.log( E_INFO, "[ '" + task_name + "' ] Identity context set as user '" + user_name + "' and group '" + group_name + "'." );
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute our big nasty thing
|
||||||
|
int ret = execl("/bin/sh", "/bin/sh", "-c", sourcer.c_str(), (char *) NULL);
|
||||||
|
|
||||||
|
// print something useful to debug with if execl fails
|
||||||
|
slog.log(E_FATAL, "ret code: " + std::to_string(ret) + "; errno: " + strerror(errno));
|
||||||
|
// exit child -- if this is executing, you've had a failure
|
||||||
|
|
||||||
|
exit(exit_code_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
PARENT:
|
||||||
|
{
|
||||||
|
// enter the parent process
|
||||||
|
close(child_stdout_pipe[WRITE_END]);
|
||||||
|
close(child_stderr_pipe[WRITE_END]);
|
||||||
|
|
||||||
|
// buffers for reading from child fd's
|
||||||
|
char stdout_buf[1000] = {0};
|
||||||
|
char stderr_buf[1000] = {0};
|
||||||
|
|
||||||
|
// will contain a set of file descriptors to monitor representing stdout and stderr of the child process
|
||||||
|
fd_set readfds;
|
||||||
|
|
||||||
|
// loop completion flags
|
||||||
|
bool set_stdout_break = false;
|
||||||
|
bool set_stderr_break = false;
|
||||||
|
|
||||||
|
// read from fd until child completes -- signaled by stdout_break and stderr_break flags
|
||||||
|
while ((! set_stderr_break ) or (! set_stdout_break)) {
|
||||||
|
// clear it out to make sure it's clean
|
||||||
|
FD_ZERO( & readfds );
|
||||||
|
|
||||||
|
// add child stdout and stderr pipes (read end)
|
||||||
|
FD_SET( child_stdout_pipe[READ_END], & readfds );
|
||||||
|
FD_SET( child_stderr_pipe[READ_END], & readfds );
|
||||||
|
|
||||||
|
// for some reason select needs the highest number of the fd +1 of its own input
|
||||||
|
int highest_fd = child_stderr_pipe[READ_END] > child_stdout_pipe[READ_END] ? child_stderr_pipe[READ_END] : child_stdout_pipe[READ_END];
|
||||||
|
|
||||||
|
// wait for one of the fd's to become readable
|
||||||
|
if ( select( highest_fd + 1, &readfds, NULL, NULL, NULL ) >= 0 )
|
||||||
|
{ // can read any
|
||||||
|
if ( FD_ISSET( child_stdout_pipe[READ_END], &readfds ) )
|
||||||
|
{ // can read child stdout pipe
|
||||||
|
// read and return the byte size of what was read
|
||||||
|
int stdout_count = read(child_stdout_pipe[READ_END], stdout_buf, sizeof(stdout_buf) - 1);
|
||||||
|
|
||||||
|
switch(stdout_count) { // switch on the count size to allow for error return handling
|
||||||
|
case READ_PIPEOPEN_O_NONBLOCK:
|
||||||
|
if ( errno == EINTR ) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
slog.log(E_FATAL, "Pipe reading issue with child STDOUT.");
|
||||||
|
exit( PIPE_FAILED2 );
|
||||||
|
}
|
||||||
|
case READ_EOF:
|
||||||
|
// signal that STDOUT is complete
|
||||||
|
set_stdout_break = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if ( log_to_file ) {
|
||||||
|
tee_out.write( stdout_buf, stdout_count );
|
||||||
|
tee_out.flush();
|
||||||
|
} else {
|
||||||
|
std::cout.write( stdout_buf, stdout_count );
|
||||||
|
std::cout.flush();
|
||||||
|
}
|
||||||
|
// clear the buffer to prevent artifacts from previous loop
|
||||||
|
memset( &stdout_buf[0], 0, sizeof( stdout_buf ) -1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( FD_ISSET( child_stderr_pipe[READ_END], & readfds ) ) {
|
||||||
|
// can read child stderr pipe
|
||||||
|
// so do so
|
||||||
|
|
||||||
|
// read and return the byte size of what was read
|
||||||
|
int stderr_count = read( child_stderr_pipe[READ_END], stderr_buf, sizeof( stderr_buf ) -1 );
|
||||||
|
|
||||||
|
// switch on the count size to allow for error return handling
|
||||||
|
switch( stderr_count ) {
|
||||||
|
case READ_RESULTS::READ_PIPEOPEN_O_NONBLOCK:
|
||||||
|
if ( errno == EINTR ) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
perror( "read stderr" );
|
||||||
|
slog.log( E_FATAL, "Pipe reading issue with child STDERR." );
|
||||||
|
exit( PIPE_FAILED3 );
|
||||||
|
}
|
||||||
|
case READ_RESULTS::READ_EOF:
|
||||||
|
// let the loop know the STDERR criteria has been met
|
||||||
|
set_stderr_break = true;
|
||||||
|
// continue the loop
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
// write the buffer contents to the STDERR Tee
|
||||||
|
tee_err.write( stderr_buf, stderr_count );
|
||||||
|
// flush the TEE
|
||||||
|
tee_err.flush();
|
||||||
|
// clear the buffer to prevent artifacts from previous loop
|
||||||
|
memset( &stderr_buf[0], 0, sizeof( stderr_buf ) -1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// select error, fatal, throw
|
||||||
|
slog.log( E_FATAL, "Fatal error, Unknown.");
|
||||||
|
} // end select/if
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the child to exit
|
||||||
|
while ( ( pid = waitpid(pid, &exit_code_raw, 0 ) ) == -1 ) {}
|
||||||
|
|
||||||
|
// clean up Tee
|
||||||
|
stdout_log.close();
|
||||||
|
stderr_log.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WEXITSTATUS( exit_code_raw );
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,18 +19,77 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_SPROC_H
|
#ifndef REX_SPROCKET_H
|
||||||
#define FTESTS_SPROC_H
|
#define REX_SPROCKET_H
|
||||||
|
|
||||||
#include <string>
|
#include "../Logger/Logger.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "errno.h"
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <streambuf>
|
||||||
|
#include "unistd.h"
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include "fcntl.h"
|
||||||
|
|
||||||
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
// exit codes for Rex
|
||||||
// should be able to recieve path of binary to be executed as well as any parameters
|
enum SPROC_RETURN_CODES {
|
||||||
class Sproc {
|
SUCCESS = true,
|
||||||
public:
|
UID_NOT_FOUND = -404,
|
||||||
// call the object. returnvalue is enum representing external execution attempt not binary exit code
|
GID_NOT_FOUND = -405,
|
||||||
static int execute( std::string input );
|
SET_GID_FAILED = -401,
|
||||||
|
SET_UID_FAILED = -404,
|
||||||
|
EXEC_FAILURE_GENERAL = -666,
|
||||||
|
PIPE_FAILED3 = -996,
|
||||||
|
PIPE_FAILED2 = -997,
|
||||||
|
PIPE_FAILED = -998,
|
||||||
|
DUP2_FAILED = -999,
|
||||||
|
FORK_FAILED = -1000
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_SPROC_H
|
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
||||||
|
// should be able to receive path of binary to be executed as well as any parameters
|
||||||
|
class Sproc {
|
||||||
|
public:
|
||||||
|
// call the object. return value is enum representing external execution attempt not binary exit code
|
||||||
|
static int execute(
|
||||||
|
std::string shell,
|
||||||
|
std::string environment_file,
|
||||||
|
std::string user_name,
|
||||||
|
std::string group_name,
|
||||||
|
std::string command,
|
||||||
|
int LOG_LEVEL,
|
||||||
|
std::string task_name,
|
||||||
|
bool log_to_file,
|
||||||
|
std::string logs_dir
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// a teebuff implementation
|
||||||
|
class teebuf: public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Construct a streambuf which tees output to both input
|
||||||
|
// streambufs.
|
||||||
|
teebuf(std::streambuf * sb1, std::streambuf * sb2);
|
||||||
|
private:
|
||||||
|
virtual int overflow(int c);
|
||||||
|
virtual int sync();
|
||||||
|
std::streambuf * sb1;
|
||||||
|
std::streambuf * sb2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// a simple helper class to create a tee stream from two input streams
|
||||||
|
class teestream : public std::ostream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Construct an ostream which tees output to the supplied ostreams.
|
||||||
|
teestream( std::ostream & o1, std::ostream & o2);
|
||||||
|
teebuf tbuf;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //REX_SPROCKET_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -66,7 +67,7 @@ protected:
|
|||||||
/// TODO Expand to detect when a directory path is supplied for units_path or plan_path and import all Tasks and Units.
|
/// 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.
|
/// \param filename - The filename to load the configuration from.
|
||||||
Conf::Conf( std::string filename, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_conf" )
|
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
|
||||||
{
|
{
|
||||||
this->LOG_LEVEL = LOG_LEVEL;
|
this->LOG_LEVEL = LOG_LEVEL;
|
||||||
|
|
||||||
@@ -91,18 +92,18 @@ Conf::Conf( std::string filename, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slo
|
|||||||
throw ConfigLoadException("config_version string expected was " + std::string(VERSION_STRING) + " in: " + filename);
|
throw ConfigLoadException("config_version string expected was " + std::string(VERSION_STRING) + " in: " + filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the path to the plan file
|
|
||||||
if (this->get_serialized(this->plan_path, "plan_path" ) != 0 )
|
|
||||||
{
|
|
||||||
throw ConfigLoadException("plan_path string is not set in the config file supplied:" + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the path to the unit definitions file
|
// find the path to the unit definitions file
|
||||||
if (this->get_serialized(this->units_path, "units_path" ) != 0 )
|
if (this->get_serialized(this->units_path, "units_path" ) != 0 )
|
||||||
{
|
{
|
||||||
throw ConfigLoadException("units_path string is not set in the config file supplied: " + filename);
|
throw ConfigLoadException("units_path string is not set in the config file supplied: " + filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find the path to logs directory
|
||||||
|
if (this->get_serialized(this->logs_path, "logs_path" ) != 0 )
|
||||||
|
{
|
||||||
|
throw ConfigLoadException("logs_path string is not set in the config file supplied: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
if ( this->get_serialized(this->override_execution_context, "execution_context_override" ) != 0 )
|
if ( this->get_serialized(this->override_execution_context, "execution_context_override" ) != 0 )
|
||||||
{
|
{
|
||||||
throw ConfigLoadException("execution_context_override boolean is not set in the config file supplied: " + filename);
|
throw ConfigLoadException("execution_context_override boolean is not set in the config file supplied: " + filename);
|
||||||
@@ -114,29 +115,15 @@ Conf::Conf( std::string filename, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slo
|
|||||||
{
|
{
|
||||||
throw ConfigLoadException("execution_context string is not set in the config file supplied: " + filename);
|
throw ConfigLoadException("execution_context string is not set in the config file supplied: " + filename);
|
||||||
} else {
|
} else {
|
||||||
this->execution_context_literal = this->execution_context.asString();
|
if ( is_dir( this->execution_context.asString() ) ) {
|
||||||
|
this->execution_context_literal = this->execution_context.asString();
|
||||||
|
} else {
|
||||||
|
throw ConfigLoadException( "The execution context supplied is an invalid directory.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this->get_serialized(this->env_vars_file, "env_vars_file" ) != 0 )
|
|
||||||
{
|
|
||||||
throw ConfigLoadException("env_vars_file is not set in the config file supplied: " + filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->env_vars_file_literal = this->execution_context_literal + "/" + this->env_vars_file.asString();
|
|
||||||
|
|
||||||
if ( exists( this->get_env_vars_file() ) )
|
|
||||||
{
|
|
||||||
this->slog.log( E_DEBUG, "Environment variables file exists: '" + this->get_env_vars_file() + "'." );
|
|
||||||
} else {
|
|
||||||
this->slog.log( E_FATAL, "Variables file does not exist: '" + this->env_vars_file_literal + "'.");
|
|
||||||
throw ConfigLoadException( "env_vars_file points to an incorrect path." );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Conf::has_context_override - Specifies whether or not the override context function is enabled in the conf file.
|
/// Conf::has_context_override - Specifies whether or not the override context function is enabled in the Conf file.
|
||||||
bool Conf::has_context_override() {
|
bool Conf::has_context_override() {
|
||||||
return this->override_execution_context.asBool();
|
return this->override_execution_context.asBool();
|
||||||
}
|
}
|
||||||
@@ -146,19 +133,15 @@ std::string Conf::get_execution_context() {
|
|||||||
return this->execution_context_literal;
|
return this->execution_context_literal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// 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(); }
|
std::string Conf::get_units_path() { return this->units_path.asString(); }
|
||||||
|
|
||||||
|
/// Conf::get_units_path - Retrieves the path to the Unit definition file from the application configuration file.
|
||||||
|
std::string Conf::get_logs_path() { return this->logs_path.asString(); }
|
||||||
|
|
||||||
/// Conf::set_execution_context- Sets the execution context.
|
/// Conf::set_execution_context- Sets the execution context.
|
||||||
void Conf::set_execution_context( std::string execution_context )
|
void Conf::set_execution_context(std::string execution_context )
|
||||||
{
|
{
|
||||||
this->execution_context_literal = execution_context;
|
this->execution_context_literal = execution_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Conf::get_env_vars_file()
|
|
||||||
{
|
|
||||||
return this->env_vars_file_literal;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_CONF_H
|
#ifndef REX_CONF_H
|
||||||
#define FTESTS_CONF_H
|
#define REX_CONF_H
|
||||||
#include "../low_level/JSON_Loader.h"
|
#include "../low_level/JSON_Loader.h"
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include "../../Logger/Logger.h"
|
#include "../../Logger/Logger.h"
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
#define STRINGIZE2(s) #s
|
#define STRINGIZE2(s) #s
|
||||||
#define STRINGIZE(s) STRINGIZE2(s)
|
#define STRINGIZE(s) STRINGIZE2(s)
|
||||||
# define IMPL_CONFIG_VERSION 3
|
# define IMPL_CONFIG_VERSION 4
|
||||||
# define VERSION_STRING STRINGIZE(IMPL_CONFIG_VERSION)
|
# define VERSION_STRING STRINGIZE(IMPL_CONFIG_VERSION)
|
||||||
|
|
||||||
class Conf: public JSON_Loader
|
class Conf: public JSON_Loader
|
||||||
@@ -37,34 +38,31 @@ private:
|
|||||||
Json::Value units_path;
|
Json::Value units_path;
|
||||||
Json::Value execution_context;
|
Json::Value execution_context;
|
||||||
Json::Value config_version;
|
Json::Value config_version;
|
||||||
Json::Value env_vars_file;
|
Json::Value logs_path;
|
||||||
|
|
||||||
std::string env_vars_file_literal;
|
|
||||||
|
|
||||||
// flag to indicate if execution context should be overriden in config file
|
// flag to indicate if execution context should be overriden in config file
|
||||||
// if set to true Examplar should use whats in the config file for current working directory
|
// if set to true rex should use whats in the config file for current working directory
|
||||||
// if set to false, Examplar should use the current working directory at time of execution
|
// if set to false, rex should use the current working directory at time of execution
|
||||||
Json::Value override_execution_context;
|
Json::Value override_execution_context;
|
||||||
|
|
||||||
bool override_context;
|
bool override_context;
|
||||||
std::string execution_context_literal;
|
std::string execution_context_literal;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Conf( std::string filename, int LOG_LEVEL );
|
Conf(std::string filename, int LOG_LEVEL );
|
||||||
|
|
||||||
bool has_context_override();
|
bool has_context_override();
|
||||||
|
|
||||||
std::string get_plan_path();
|
|
||||||
std::string get_units_path();
|
std::string get_units_path();
|
||||||
std::string get_execution_context();
|
std::string get_execution_context();
|
||||||
|
std::string get_logs_path();
|
||||||
|
|
||||||
void set_execution_context( std::string );
|
void set_execution_context( std::string );
|
||||||
|
|
||||||
std::string get_env_vars_file();
|
|
||||||
private:
|
private:
|
||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
Logger slog;
|
Logger slog;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_CONF_H
|
#endif //REX_CONF_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -120,7 +121,7 @@ protected:
|
|||||||
/// Plan::Plan() - Constructor for Plan class. A Plan is a managed container for a Task vector. These tasks reference
|
/// 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
|
/// 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.
|
/// definitions to execute, and if Units together form a Suite, Tasks together form a Plan.
|
||||||
Plan::Plan( Conf * configuration, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_plan" )
|
Plan::Plan(Conf * configuration, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_plan_" )
|
||||||
{
|
{
|
||||||
this->configuration = configuration;
|
this->configuration = configuration;
|
||||||
this->LOG_LEVEL = LOG_LEVEL;
|
this->LOG_LEVEL = LOG_LEVEL;
|
||||||
@@ -257,17 +258,17 @@ void Plan::execute()
|
|||||||
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
|
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
|
||||||
{
|
{
|
||||||
|
|
||||||
this->slog.log( E_INFO, "Executing task \"" + this->tasks[i].get_name() + "\"." );
|
this->slog.log( E_INFO, "[ '" + this->tasks[i].get_name() + "' ] Executing..." );
|
||||||
try {
|
try {
|
||||||
this->tasks[i].execute( this->configuration );
|
this->tasks[i].execute( this->configuration );
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
catch (std::exception& e) {
|
||||||
this->slog.log( E_FATAL, "Plan Task: \"" + this->tasks[i].get_name() + "\" reported: " + e.what() );
|
this->slog.log( E_FATAL, "[ '" + this->tasks[i].get_name() + "' ] Report: " + e.what() );
|
||||||
throw Plan_Task_GeneralExecutionException("Could not execute task.");
|
throw Plan_Task_GeneralExecutionException("Could not execute task.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// not all deps met for this task
|
// not all deps met for this task
|
||||||
this->slog.log( E_FATAL, "Plan Task \"" + this->tasks[i].get_name() + "\" was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
|
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." );
|
throw Plan_Task_Missing_Dependency( "Unmet dependency for task." );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_PLAN_H
|
#ifndef REX_PLAN_H
|
||||||
#define FTESTS_PLAN_H
|
#define REX_PLAN_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../../json/json.h"
|
#include "../../json/json.h"
|
||||||
@@ -36,7 +37,7 @@ class Plan: public JSON_Loader
|
|||||||
Conf * configuration;
|
Conf * configuration;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Plan( Conf * configuration, int LOG_LEVEL );
|
Plan(Conf * configuration, int LOG_LEVEL );
|
||||||
|
|
||||||
// append this->tasks from JSON file
|
// append this->tasks from JSON file
|
||||||
void load_plan_file( std::string filename );
|
void load_plan_file( std::string filename );
|
||||||
@@ -63,4 +64,4 @@ private:
|
|||||||
Logger slog;
|
Logger slog;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_PLAN_H
|
#endif //REX_PLAN_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -79,7 +80,7 @@ protected:
|
|||||||
/// human processes to allow modularly developed profiles of test suites. As inferred, Unit is expected to be one of
|
/// 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
|
/// 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.
|
/// if the implementor so desires.
|
||||||
Suite::Suite( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_suite" )
|
Suite::Suite( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_suite" )
|
||||||
{
|
{
|
||||||
this->LOG_LEVEL;
|
this->LOG_LEVEL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_UNITS_H
|
#ifndef REX_UNITS_H
|
||||||
#define FTESTS_UNITS_H
|
#define REX_UNITS_H
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "../../json/json.h"
|
#include "../../json/json.h"
|
||||||
@@ -54,4 +55,4 @@ private:
|
|||||||
Logger slog;
|
Logger slog;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_UNITS_H
|
#endif //REX_UNITS_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -30,7 +31,7 @@ public:
|
|||||||
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
||||||
class Task_NotReady: public std::runtime_error {
|
class Task_NotReady: public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
Task_NotReady(): std::runtime_error("Task: Attempted to access a unit of a Task that is not defined.") {}
|
Task_NotReady(): std::runtime_error("Task: Attempted to execute a Task whose Unit is not well defined.") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ protected:
|
|||||||
/// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to
|
/// 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.
|
/// execute, and its dependencies on other units to have already been completed successfully.
|
||||||
Task::Task( int LOG_LEVEL ):
|
Task::Task( int LOG_LEVEL ):
|
||||||
slog( LOG_LEVEL, "e_task" ),
|
slog( LOG_LEVEL, "_task_" ),
|
||||||
definition( LOG_LEVEL )
|
definition( LOG_LEVEL )
|
||||||
{
|
{
|
||||||
// it hasn't executed yet.
|
// it hasn't executed yet.
|
||||||
@@ -132,7 +133,7 @@ std::string Task::get_name()
|
|||||||
void Task::load_definition( Unit selected_unit )
|
void Task::load_definition( Unit selected_unit )
|
||||||
{
|
{
|
||||||
this->definition = selected_unit;
|
this->definition = selected_unit;
|
||||||
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" for task \"" + this->get_name() + "\".");
|
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan.");
|
||||||
this->defined = true;
|
this->defined = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,9 +166,9 @@ bool Task::has_definition()
|
|||||||
/// Task::execute - execute a task's unit definition.
|
/// Task::execute - execute a task's unit definition.
|
||||||
/// See the design document for what flow control needs to look like here.
|
/// See the design document for what flow control needs to look like here.
|
||||||
/// \param verbose - Verbosity level - not implemented yet.
|
/// \param verbose - Verbosity level - not implemented yet.
|
||||||
void Task::execute( Conf * configuration )
|
void Task::execute(Conf * configuration )
|
||||||
{
|
{
|
||||||
// DUFFING - If Examplar is broken it's probably going to be in this block.
|
// DUFFING - If rex is broken it's probably going to be in this block.
|
||||||
// Somebody come clean this up, eh?
|
// Somebody come clean this up, eh?
|
||||||
|
|
||||||
// PREWORK
|
// PREWORK
|
||||||
@@ -180,27 +181,49 @@ void Task::execute( Conf * configuration )
|
|||||||
|
|
||||||
// get the name
|
// get the name
|
||||||
std::string task_name = this->definition.get_name();
|
std::string task_name = this->definition.get_name();
|
||||||
this->slog.log( E_DEBUG, "Using unit: \"" + task_name + "\"." );
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Using unit definition: \"" + task_name + "\"." );
|
||||||
// END PREWORK
|
// END PREWORK
|
||||||
|
|
||||||
// get the target execution command
|
// get the target execution command
|
||||||
std::string target_command = configuration->get_execution_context() + + "/" + this->definition.get_target();
|
std::string target_command = this->definition.get_target();
|
||||||
|
|
||||||
// check if context override
|
// check if context override
|
||||||
if ( configuration->has_context_override() )
|
if ( configuration->has_context_override() )
|
||||||
{
|
{
|
||||||
// if so, set the CWD.
|
// if so, set the CWD.
|
||||||
chdir( configuration->get_execution_context().c_str() );
|
chdir( configuration->get_execution_context().c_str() );
|
||||||
this->slog.log( E_INFO, "Setting execution context: " + get_working_path() );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Setting execution context: " + get_working_path() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// a[0] execute target
|
// a[0] execute target
|
||||||
// TODO revise variable sourcing strategy
|
// TODO revise variable sourcing strategy
|
||||||
|
// ....sourcing on the shell for variables and environment population doesn't have a good smell.
|
||||||
|
|
||||||
this->slog.log( E_INFO, "Executing target: \"" + target_command + "\"." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing target: \"" + target_command + "\"." );
|
||||||
this->slog.log( E_DEBUG, "Vars file: " + configuration->get_env_vars_file() );
|
if ( exists( target_command ) )
|
||||||
int return_code = Sproc::execute( ". " + configuration->get_env_vars_file() + " && " + target_command );
|
{
|
||||||
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Target executable found.");
|
||||||
|
} else {
|
||||||
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Target executable does not exist." );
|
||||||
|
throw Task_NotReady();
|
||||||
|
}
|
||||||
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Vars file: " + this->definition.get_env_vars_file() );
|
||||||
|
this->slog.log( E_DEBUG, "[ '" + task_name + "' ] Shell: " + this->definition.get_shell() );
|
||||||
|
|
||||||
|
|
||||||
|
std::string static_env_file = configuration->get_execution_context() + "/" + this->definition.get_env_vars_file();
|
||||||
|
int return_code = Sproc::execute(
|
||||||
|
this->definition.get_shell(),
|
||||||
|
static_env_file,
|
||||||
|
this->definition.get_user(),
|
||||||
|
this->definition.get_group(),
|
||||||
|
target_command,
|
||||||
|
this->LOG_LEVEL,
|
||||||
|
task_name,
|
||||||
|
this->definition.get_stdout_log_flag(),
|
||||||
|
configuration->get_logs_path()
|
||||||
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[0] Error Code Check
|
// d[0] Error Code Check
|
||||||
@@ -208,7 +231,7 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( return_code == 0 )
|
if ( return_code == 0 )
|
||||||
{
|
{
|
||||||
// d[0].0 ZERO
|
// d[0].0 ZERO
|
||||||
this->slog.log( E_INFO, "Target \"" + task_name + "\" succeeded. Marking as complete." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Target succeeded. Marking as complete." );
|
||||||
|
|
||||||
this->mark_complete();
|
this->mark_complete();
|
||||||
|
|
||||||
@@ -219,7 +242,7 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( return_code != 0 )
|
if ( return_code != 0 )
|
||||||
{
|
{
|
||||||
// d[0].1 NON-ZERO
|
// d[0].1 NON-ZERO
|
||||||
this->slog.log( E_WARN, "Target \"" + task_name + "\" failed with exit code " + std::to_string( return_code ) + "." );
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Target failed with exit code " + std::to_string( return_code ) + "." );
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[1] Rectify Check
|
// d[1] Rectify Check
|
||||||
@@ -235,12 +258,12 @@ void Task::execute( Conf * configuration )
|
|||||||
{
|
{
|
||||||
// d[2].0 FALSE
|
// d[2].0 FALSE
|
||||||
// a[2] NEXT
|
// a[2] NEXT
|
||||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// d[2].1 TRUE
|
// d[2].1 TRUE
|
||||||
// a[3] EXCEPTION
|
// a[3] EXCEPTION
|
||||||
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, and failed, and rectification is not enabled." );
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, and rectification is not enabled." );
|
||||||
throw TaskException( "Task failed: " + task_name );
|
throw TaskException( "Task failed: " + task_name );
|
||||||
}
|
}
|
||||||
// **********************************************
|
// **********************************************
|
||||||
@@ -252,14 +275,24 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( this->definition.get_rectify() )
|
if ( this->definition.get_rectify() )
|
||||||
{
|
{
|
||||||
// d[1].1 TRUE (Rectify Check)
|
// d[1].1 TRUE (Rectify Check)
|
||||||
this->slog.log( E_INFO, "Rectification pattern is enabled for \"" + task_name + "\"." );
|
this->slog.log( E_INFO, "[ " + task_name + " ] Rectification pattern is enabled." );
|
||||||
|
|
||||||
// a[4] Execute RECTIFIER
|
// a[4] Execute RECTIFIER
|
||||||
std::string rectifier_command = this->definition.get_rectifier();
|
std::string rectifier_command = this->definition.get_rectifier();
|
||||||
|
|
||||||
this->slog.log( E_INFO, "Executing rectification: " + rectifier_command + "." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Executing rectification: " + rectifier_command + "." );
|
||||||
|
|
||||||
int rectifier_error = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + rectifier_command );
|
int rectifier_error = Sproc::execute(
|
||||||
|
this->definition.get_shell(),
|
||||||
|
static_env_file,
|
||||||
|
this->definition.get_user(),
|
||||||
|
this->definition.get_group(),
|
||||||
|
rectifier_command,
|
||||||
|
this->LOG_LEVEL,
|
||||||
|
task_name,
|
||||||
|
this->definition.get_stdout_log_flag(),
|
||||||
|
configuration->get_logs_path()
|
||||||
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[3] Error Code Check for Rectifier
|
// d[3] Error Code Check for Rectifier
|
||||||
@@ -267,7 +300,7 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( rectifier_error != 0 )
|
if ( rectifier_error != 0 )
|
||||||
{
|
{
|
||||||
// d[3].1 Non-Zero
|
// d[3].1 Non-Zero
|
||||||
this->slog.log( E_WARN, "Rectification of \"" + task_name + "\" failed with exit code " + std::to_string( rectifier_error ) + "." );
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Rectification failed with exit code " + std::to_string( rectifier_error ) + "." );
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[4] Required Check
|
// d[4] Required Check
|
||||||
@@ -275,7 +308,7 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( ! this->definition.get_required() ) {
|
if ( ! this->definition.get_required() ) {
|
||||||
// d[4].0 FALSE
|
// d[4].0 FALSE
|
||||||
// a[5] NEXT
|
// a[5] NEXT
|
||||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] This task is not required to continue the plan. Moving on." );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +316,7 @@ void Task::execute( Conf * configuration )
|
|||||||
{
|
{
|
||||||
// d[4].1 TRUE
|
// d[4].1 TRUE
|
||||||
// a[6] EXCEPTION
|
// a[6] EXCEPTION
|
||||||
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, it failed, and then rectification failed. Lost cause." );
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, but failed, and rectification failed. Lost cause." );
|
||||||
throw TaskException( "Lost cause, task failure." );
|
throw TaskException( "Lost cause, task failure." );
|
||||||
}
|
}
|
||||||
// **********************************************
|
// **********************************************
|
||||||
@@ -295,12 +328,22 @@ void Task::execute( Conf * configuration )
|
|||||||
if ( rectifier_error == 0 )
|
if ( rectifier_error == 0 )
|
||||||
{
|
{
|
||||||
// d[3].0 Zero
|
// d[3].0 Zero
|
||||||
this->slog.log( E_INFO, "Rectification returned successfully." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Rectification returned successfully." );
|
||||||
|
|
||||||
// a[7] Re-execute Target
|
// a[7] Re-execute Target
|
||||||
this->slog.log( E_INFO, "Re-Executing target \"" + this->definition.get_target() + "\"." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-Executing target \"" + this->definition.get_target() + "\"." );
|
||||||
|
|
||||||
int retry_code = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + target_command );
|
int retry_code = Sproc::execute(
|
||||||
|
this->definition.get_shell(),
|
||||||
|
static_env_file,
|
||||||
|
this->definition.get_user(),
|
||||||
|
this->definition.get_group(),
|
||||||
|
target_command,
|
||||||
|
this->LOG_LEVEL,
|
||||||
|
task_name,
|
||||||
|
this->definition.get_stdout_log_flag(),
|
||||||
|
configuration->get_logs_path()
|
||||||
|
);
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[5] Error Code Check
|
// d[5] Error Code Check
|
||||||
@@ -309,11 +352,11 @@ void Task::execute( Conf * configuration )
|
|||||||
{
|
{
|
||||||
// d[5].0 ZERO
|
// d[5].0 ZERO
|
||||||
// a[8] NEXT
|
// a[8] NEXT
|
||||||
this->slog.log( E_INFO, "Re-execution was successful." );
|
this->slog.log( E_INFO, "[ '" + task_name + "' ] Re-execution was successful." );
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// d[5].1 NON-ZERO
|
// d[5].1 NON-ZERO
|
||||||
this->slog.log( E_WARN, "Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
this->slog.log( E_WARN, "[ '" + task_name + "' ] Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
||||||
|
|
||||||
// **********************************************
|
// **********************************************
|
||||||
// d[6] Required Check
|
// d[6] Required Check
|
||||||
@@ -330,7 +373,7 @@ void Task::execute( Conf * configuration )
|
|||||||
{
|
{
|
||||||
// d[6].1 TRUE
|
// d[6].1 TRUE
|
||||||
// a[10] EXCEPTION
|
// a[10] EXCEPTION
|
||||||
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
this->slog.log( E_FATAL, "[ '" + task_name + "' ] Task is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
||||||
throw TaskException( "Lost cause, task failure." );
|
throw TaskException( "Lost cause, task failure." );
|
||||||
}
|
}
|
||||||
// **********************************************
|
// **********************************************
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_TASK_H
|
#ifndef REX_TASK_H
|
||||||
#define FTESTS_TASK_H
|
#define REX_TASK_H
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "../../json/json.h"
|
#include "../../json/json.h"
|
||||||
@@ -67,7 +68,7 @@ class Task
|
|||||||
std::string get_name();
|
std::string get_name();
|
||||||
|
|
||||||
// execute this task's definition
|
// execute this task's definition
|
||||||
void execute( Conf * configuration );
|
void execute(Conf * configuration );
|
||||||
|
|
||||||
void mark_complete();
|
void mark_complete();
|
||||||
|
|
||||||
@@ -79,4 +80,4 @@ private:
|
|||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_TASK_H
|
#endif //REX_TASK_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -23,17 +24,49 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <grp.h>
|
||||||
|
|
||||||
/// Unit_NotPopulated - Meant to be thrown when a Unit type is not populated before being used.
|
/// UnitException -
|
||||||
/// Signaled by use of the 'populated' boolean member of the Unit class.
|
class UnitException: public std::exception
|
||||||
class Unit_NotPopulated: public std::runtime_error { public:
|
{
|
||||||
Unit_NotPopulated(): std::runtime_error("Unit: Attempted to access a member before loading values.") {}
|
public:
|
||||||
};
|
/** Constructor (C strings).
|
||||||
|
* @param message C-style string error message.
|
||||||
|
* The string contents are copied upon construction.
|
||||||
|
* Hence, responsibility for deleting the char* lies
|
||||||
|
* with the caller.
|
||||||
|
*/
|
||||||
|
explicit UnitException(const char* message):
|
||||||
|
msg_(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// Unit_DataStructureException - Meant to be thrown when a Unit type is accessing a member that does not exist.
|
/** Constructor (C++ STL strings).
|
||||||
class Unit_DataStructureException: public std::runtime_error { public:
|
* @param message The error message.
|
||||||
// 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.") {}
|
explicit UnitException(const std::string& message):
|
||||||
|
msg_(message)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/** Destructor.
|
||||||
|
* Virtual to allow for subclassing.
|
||||||
|
*/
|
||||||
|
virtual ~UnitException() throw (){}
|
||||||
|
|
||||||
|
/** Returns a pointer to the (constant) error description.
|
||||||
|
* @return A pointer to a const char*. The underlying memory
|
||||||
|
* is in posession of the Exception object. Callers must
|
||||||
|
* not attempt to free the memory.
|
||||||
|
*/
|
||||||
|
virtual const char* what() const throw (){
|
||||||
|
return msg_.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Error message.
|
||||||
|
*/
|
||||||
|
std::string msg_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Unit::Unit - Constructor for Unit type. The Unit is a definition of an automation task. Each Unit has:
|
/// Unit::Unit - Constructor for Unit type. The Unit is a definition of an automation task. Each Unit has:
|
||||||
@@ -48,7 +81,7 @@ class Unit_DataStructureException: public std::runtime_error { public:
|
|||||||
/// required, which is used as a flag to halt or continue if rectifier does not heal the system in such a way that
|
/// 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.
|
/// target can run successfully.
|
||||||
/// rectify, which is used as a flag to determine in the rectifier runs.
|
/// rectify, which is used as a flag to determine in the rectifier runs.
|
||||||
Unit::Unit( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_unit" )
|
Unit::Unit( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "_unit_" )
|
||||||
{
|
{
|
||||||
this->LOG_LEVEL;
|
this->LOG_LEVEL;
|
||||||
}
|
}
|
||||||
@@ -66,22 +99,79 @@ int Unit::load_root(Json::Value loader_root)
|
|||||||
// TODO this pattern is 'working but broken'. need to use a datastructure for required members and iterate
|
// 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
|
// do NOT replace this with a switch case pattern
|
||||||
if ( loader_root.isMember("name") )
|
if ( loader_root.isMember("name") )
|
||||||
{ this->name = loader_root.get("name", errmsg).asString(); } else throw Unit_DataStructureException();
|
{ this->name = loader_root.get("name", errmsg).asString(); } else
|
||||||
|
throw UnitException("No name attribute specified when loading a unit.");
|
||||||
|
|
||||||
if ( loader_root.isMember("target") )
|
if ( loader_root.isMember("target") )
|
||||||
{ this->target = loader_root.get("target", errmsg).asString(); } else throw Unit_DataStructureException();
|
{ this->target = loader_root.get("target", errmsg).asString(); } else
|
||||||
|
throw UnitException("No target attribute specified when loading a unit.");
|
||||||
|
|
||||||
if ( loader_root.isMember("rectifier") )
|
if ( loader_root.isMember("rectifier") )
|
||||||
{ this->rectifier = loader_root.get("rectifier", errmsg).asString(); } else throw Unit_DataStructureException();
|
{ 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") )
|
if ( loader_root.isMember("active") )
|
||||||
{ this->active = loader_root.get("active", errmsg).asBool(); } else throw Unit_DataStructureException();
|
{ this->active = loader_root.get("active", errmsg).asBool(); } else
|
||||||
|
throw UnitException("No activation attribute specified when loading a unit.");
|
||||||
|
|
||||||
if ( loader_root.isMember("required") )
|
if ( loader_root.isMember("required") )
|
||||||
{ this->required = loader_root.get("required", errmsg).asBool(); } else throw Unit_DataStructureException();
|
{ this->required = loader_root.get("required", errmsg).asBool(); } else
|
||||||
|
throw UnitException("No required attribute specified when loading a unit.");
|
||||||
|
|
||||||
|
if ( loader_root.isMember("log") )
|
||||||
|
{ this->stdout_log_flag = loader_root.get("log", errmsg).asBool(); } else
|
||||||
|
throw UnitException("No log attribute specified when loading a unit.");
|
||||||
|
|
||||||
if ( loader_root.isMember("rectify") )
|
if ( loader_root.isMember("rectify") )
|
||||||
{ this->rectify = loader_root.get("rectify", errmsg).asBool(); } else throw Unit_DataStructureException();
|
{ this->rectify = loader_root.get("rectify", errmsg).asBool(); } else
|
||||||
|
throw UnitException("No rectify boolean attribute specified when loading a unit.");
|
||||||
|
|
||||||
|
// TODO functionize this
|
||||||
|
int uid = getuid();
|
||||||
|
struct passwd * upw;
|
||||||
|
|
||||||
|
std::string errmsg_user;
|
||||||
|
|
||||||
|
// if no user field is specified then default to the currently executing user
|
||||||
|
if ( ( upw = getpwuid(uid) ) == NULL )
|
||||||
|
{
|
||||||
|
throw UnitException( "Could not retrieve current user." );
|
||||||
|
} else {
|
||||||
|
errmsg_user = upw->pw_name;
|
||||||
|
}
|
||||||
|
// -TODO
|
||||||
|
|
||||||
|
|
||||||
|
if ( loader_root.isMember( "user" ) )
|
||||||
|
{ this->user = loader_root.get( "user", errmsg_user ).asString(); } else this->user = errmsg_user;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO functionalize this
|
||||||
|
// get the current context gid as a backup value
|
||||||
|
int gid = getgid();
|
||||||
|
// declare the grp object to pull the name from once populated
|
||||||
|
struct group * grp;
|
||||||
|
// storage for backup value once retrieved
|
||||||
|
std::string errmsg_group;
|
||||||
|
|
||||||
|
// get the backup value and store it to errmsg_group
|
||||||
|
if ( ( grp = getgrgid( gid ) ) == NULL )
|
||||||
|
{
|
||||||
|
throw UnitException("Could not retrieve current group");
|
||||||
|
} else {
|
||||||
|
errmsg_group = grp->gr_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( loader_root.isMember( "group" ) )
|
||||||
|
{ this->group = loader_root.get( "group", errmsg_group ).asString(); } else this->group = grp->gr_name;
|
||||||
|
|
||||||
|
if ( loader_root.isMember( "shell" ) )
|
||||||
|
{ this->shell = loader_root.get( "shell", errmsg ).asString(); } else this->shell = "/usr/bin/env sh";
|
||||||
|
|
||||||
|
if ( loader_root.isMember( "environment") )
|
||||||
|
{ this->env_vars_file = loader_root.get( "environment", errmsg ).asString(); } else {
|
||||||
|
throw UnitException("No environment file specified for a unit, and environment files are required for unit definitions.");
|
||||||
|
}
|
||||||
|
|
||||||
this->populated = true;
|
this->populated = true;
|
||||||
|
|
||||||
@@ -108,7 +198,7 @@ int Unit::load_string(std::string json_val)
|
|||||||
/// \return the name of the unit.
|
/// \return the name of the unit.
|
||||||
std::string Unit::get_name()
|
std::string Unit::get_name()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->name;
|
return this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +207,7 @@ std::string Unit::get_name()
|
|||||||
/// \return the target of the unit.
|
/// \return the target of the unit.
|
||||||
std::string Unit::get_target()
|
std::string Unit::get_target()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->target;
|
return this->target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +216,7 @@ std::string Unit::get_target()
|
|||||||
/// \return the output of the unit.
|
/// \return the output of the unit.
|
||||||
std::string Unit::get_output()
|
std::string Unit::get_output()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->output;
|
return this->output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +225,7 @@ std::string Unit::get_output()
|
|||||||
/// \return the rectifier of the unit.
|
/// \return the rectifier of the unit.
|
||||||
std::string Unit::get_rectifier()
|
std::string Unit::get_rectifier()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->rectifier;
|
return this->rectifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +234,7 @@ std::string Unit::get_rectifier()
|
|||||||
/// \return the armed status of the unit.
|
/// \return the armed status of the unit.
|
||||||
bool Unit::get_active()
|
bool Unit::get_active()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->active;
|
return this->active;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +243,7 @@ bool Unit::get_active()
|
|||||||
/// \return the requirement status of the unit.
|
/// \return the requirement status of the unit.
|
||||||
bool Unit::get_required()
|
bool Unit::get_required()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->required;
|
return this->required;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +252,51 @@ bool Unit::get_required()
|
|||||||
/// \return the rectification status of the unit.
|
/// \return the rectification status of the unit.
|
||||||
bool Unit::get_rectify()
|
bool Unit::get_rectify()
|
||||||
{
|
{
|
||||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
return this->rectify;
|
return this->rectify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unit::get_user - retrieves the user context for the unit.
|
||||||
|
///
|
||||||
|
/// \return the string value of the user name.
|
||||||
|
std::string Unit::get_user()
|
||||||
|
{
|
||||||
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
|
return this->user;
|
||||||
|
}
|
||||||
|
/// Unit::get_group - retrieves the group context for the unit.
|
||||||
|
///
|
||||||
|
/// \return the string value of the group name.
|
||||||
|
std::string Unit::get_group()
|
||||||
|
{
|
||||||
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
|
return this->group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unit::get_shell - retrieves the shell path to use for the unit execution.
|
||||||
|
///
|
||||||
|
/// \return the string value of the shell path.
|
||||||
|
std::string Unit::get_shell()
|
||||||
|
{
|
||||||
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
|
return this->shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unit::get_env_vars_file - retrieves the file path to use for the unit environment file. This is a file that is
|
||||||
|
/// sourced by the chosen shell to populate any environment variables.
|
||||||
|
/// \return the string value of the shell path.
|
||||||
|
std::string Unit::get_env_vars_file()
|
||||||
|
{
|
||||||
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
|
return this->env_vars_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unit::get_stdout_log_flag() - retrieves the file path to use for the unit environment file. This is a file that is
|
||||||
|
/// sourced by the chosen shell to populate any environment variables.
|
||||||
|
/// \return the string value of the shell path.
|
||||||
|
bool Unit::get_stdout_log_flag()
|
||||||
|
{
|
||||||
|
if ( ! this->populated ) { throw UnitException("Attempted to access an unpopulated unit."); }
|
||||||
|
return this->stdout_log_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -19,12 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Unit.h
|
/* Unit.h
|
||||||
* Unit is a type that represents a safely deserialized JSON object which defines what actions are taken as Examplar
|
* 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
|
* 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.
|
* which Units are executed and in what order (and which Units a given Task depends on.
|
||||||
*/
|
*/
|
||||||
#ifndef FTESTS_UNIT_H
|
#ifndef FTEST_UNIT_H
|
||||||
#define FTESTS_UNIT_H
|
#define FTEST_UNIT_H
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../../json/json.h"
|
#include "../../json/json.h"
|
||||||
#include "../low_level/JSON_Loader.h"
|
#include "../low_level/JSON_Loader.h"
|
||||||
@@ -59,6 +60,22 @@ private:
|
|||||||
// if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required
|
// if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required
|
||||||
bool rectify;
|
bool rectify;
|
||||||
|
|
||||||
|
//indicator of whether stdout should log to file. used mainly to handle glitchy TUI systems when logs are being tailed.
|
||||||
|
bool stdout_log_flag;
|
||||||
|
|
||||||
|
// user to run process as.
|
||||||
|
// not intended for protected accounts, handle your own security
|
||||||
|
std::string user;
|
||||||
|
|
||||||
|
// group to run process as.
|
||||||
|
// not intended for protected accounts, handle your own security
|
||||||
|
std::string group;
|
||||||
|
|
||||||
|
// shell to use for env
|
||||||
|
std::string shell;
|
||||||
|
|
||||||
|
std::string env_vars_file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Unit( int LOG_LEVEL );
|
Unit( int LOG_LEVEL );
|
||||||
|
|
||||||
@@ -73,13 +90,19 @@ public:
|
|||||||
std::string get_target();
|
std::string get_target();
|
||||||
std::string get_output();
|
std::string get_output();
|
||||||
std::string get_rectifier();
|
std::string get_rectifier();
|
||||||
|
std::string get_env_vars_file();
|
||||||
|
|
||||||
bool get_active();
|
bool get_active();
|
||||||
bool get_required();
|
bool get_required();
|
||||||
bool get_rectify();
|
bool get_rectify();
|
||||||
|
bool get_stdout_log_flag();
|
||||||
|
std::string get_user();
|
||||||
|
std::string get_group();
|
||||||
|
std::string get_shell();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
Logger slog;
|
Logger slog;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FTESTS_UNIT_H
|
#endif //FTEST_UNIT_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -17,12 +18,12 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
#ifndef FTESTS_LOADERS_H
|
#ifndef REX_LOADERS_H
|
||||||
#define FTESTS_LOADERS_H
|
#define REX_LOADERS_H
|
||||||
|
|
||||||
#include "../low_level/JSON_Loader.h"
|
#include "../low_level/JSON_Loader.h"
|
||||||
#include "Suite.h"
|
#include "Suite.h"
|
||||||
#include "Plan.h"
|
#include "Plan.h"
|
||||||
#include "Conf.h"
|
#include "Conf.h"
|
||||||
|
|
||||||
#endif //FTESTS_LOADERS_H
|
#endif //REX_LOADERS_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -38,7 +39,7 @@ class JSON_Loader_InvalidJSON: public std::runtime_error { public:
|
|||||||
/// JSON_Loader::JSON_Loader - Constructor for JSON_Loader base class. Simply inits to an unpopulated state.
|
/// 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.
|
/// The JSON_Loader type is a base type. It is meant to provide the functionalities shared between Suite and Plan.
|
||||||
JSON_Loader::JSON_Loader( int LOG_LEVEL ): slog( LOG_LEVEL, "e_json" )
|
JSON_Loader::JSON_Loader( int LOG_LEVEL ): slog( LOG_LEVEL, "_json_" )
|
||||||
{
|
{
|
||||||
this->populated = false;
|
this->populated = false;
|
||||||
this->LOG_LEVEL = LOG_LEVEL;
|
this->LOG_LEVEL = LOG_LEVEL;
|
||||||
@@ -91,7 +92,7 @@ void JSON_Loader::load_json_file( std::string filename )
|
|||||||
// first, check if the file exists
|
// first, check if the file exists
|
||||||
if (! exists( filename ) )
|
if (! exists( filename ) )
|
||||||
{
|
{
|
||||||
this->slog.log( E_FATAL, "File '" + filename + "' does not exist." );
|
this->slog.log( E_DEBUG, "File '" + filename + "' does not exist." );
|
||||||
throw JSON_Loader_FileNotFound();
|
throw JSON_Loader_FileNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -17,8 +18,8 @@
|
|||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
#ifndef FTESTS_JLOADER_H
|
#ifndef REX_JLOADER_H
|
||||||
#define FTESTS_JLOADER_H
|
#define REX_JLOADER_H
|
||||||
#include "../../json/json.h"
|
#include "../../json/json.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -55,4 +56,4 @@ private:
|
|||||||
Logger slog;
|
Logger slog;
|
||||||
int LOG_LEVEL;
|
int LOG_LEVEL;
|
||||||
};
|
};
|
||||||
#endif //FTESTS_JLOADER_H
|
#endif //REX_JLOADER_H
|
||||||
|
|||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -44,4 +45,14 @@ bool is_dir( std::string path )
|
|||||||
struct stat buf;
|
struct stat buf;
|
||||||
stat( path.c_str(), &buf );
|
stat( path.c_str(), &buf );
|
||||||
return S_ISDIR(buf.st_mode);
|
return S_ISDIR(buf.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_8601()
|
||||||
|
{
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto itt = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::ostringstream ss;
|
||||||
|
// ss << std::put_time(gmtime(&itt), "%FT%TZ");
|
||||||
|
ss << std::put_time(localtime(&itt), "%Y-%m-%d_%H:%M:%S");
|
||||||
|
return ss.str();
|
||||||
}
|
}
|
||||||
@@ -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
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@@ -18,13 +19,18 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FTESTS_HELPERS_H
|
#ifndef REX_HELPERS_H
|
||||||
#define FTESTS_HELPERS_H
|
#define REX_HELPERS_H
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <sstream>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
bool exists (const std::string& name);
|
bool exists (const std::string& name);
|
||||||
|
|
||||||
@@ -32,4 +38,7 @@ std::string get_working_path();
|
|||||||
bool is_file( std::string );
|
bool is_file( std::string );
|
||||||
bool is_dir( std::string );
|
bool is_dir( std::string );
|
||||||
|
|
||||||
#endif //FTESTS_HELPERS_H
|
std::string get_8601();
|
||||||
|
|
||||||
|
|
||||||
|
#endif //REX_HELPERS_JH
|
||||||
|
|||||||
7
test/components/curses_dialog.bash
Executable file
7
test/components/curses_dialog.bash
Executable file
@@ -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."
|
||||||
|
|
||||||
|
dialog --title "Dialog title" --inputbox "Enter your name:" 0 0
|
||||||
|
|
||||||
|
exit $?
|
||||||
6
test/components/dependent_test.bash
Executable file
6
test/components/dependent_test.bash
Executable file
@@ -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 $?
|
||||||
6
test/components/fail.bash
Executable file
6
test/components/fail.bash
Executable file
@@ -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
|
||||||
|
|
||||||
5
test/components/independent_test_1.bash
Executable file
5
test/components/independent_test_1.bash
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "This is an independent test. It does not depend on other tests."
|
||||||
|
echo "Environment file check: TEST_VAR from environment file is set to: $TEST_VAR"
|
||||||
|
|
||||||
|
exit $?
|
||||||
3
test/components/independent_test_2.bash
Executable file
3
test/components/independent_test_2.bash
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
echo "independent test 2 output"
|
||||||
|
echo "independent test says TEST_VAR is ${TEST_VAR}"
|
||||||
|
exit $?
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"execution_context_override": true,
|
"execution_context_override": true,
|
||||||
"execution_context": "/home/bagira/development/internal/examplar/test",
|
"execution_context": "/home/bagira/development/internal/rex/test",
|
||||||
"units_path": "units/all_test.units",
|
"units_path": "units/",
|
||||||
"plan_path": "plans/test.plan",
|
"logs_path": "logs/",
|
||||||
"config_version": "3",
|
"config_version": "4"
|
||||||
"env_vars_file": "examplar.variables"
|
|
||||||
}
|
}
|
||||||
|
|||||||
11
test/environments/examplar.variables
Normal file
11
test/environments/examplar.variables
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set -a
|
||||||
|
|
||||||
|
echo "variables file says hello and set a variable named TEST_VAR"
|
||||||
|
TEST_VAR="999"
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
echo "This is output from loading the variables file."
|
|
||||||
5
test/plans/atomic.plan
Normal file
5
test/plans/atomic.plan
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plan": [
|
||||||
|
{ "name": "independent test 1", "dependencies": [ null ] }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
"plan": [
|
"plan": [
|
||||||
{ "name": "independent test 1", "dependencies": [ null ] },
|
{ "name": "independent test 1", "dependencies": [ null ] },
|
||||||
{ "name": "independent test 2", "dependencies": [ null ] },
|
{ "name": "independent test 2", "dependencies": [ null ] },
|
||||||
{ "name": "dependent test", "dependencies": [ "independent test 1" ] }
|
{ "name": "dependent test", "dependencies": [ "independent test 1" ] },
|
||||||
|
{ "name": "curses dialog", "dependencies": [ "independent test 1" ] },
|
||||||
|
{ "name": "fail", "dependencies": [ null ] }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
5
test/stderr.log
Normal file
5
test/stderr.log
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
This test is printing to stderr.
|
||||||
|
This test is printing to stderr.
|
||||||
|
This test is printing to stderr.
|
||||||
|
This test is printing to stderr.
|
||||||
128
test/stdout.log
Normal file
128
test/stdout.log
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'independent test 1' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'independent test 1' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
TEST OUTPUT: Test var is: 999
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'independent test 2' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'independent test 2' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'independent test 2' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
independent test 2 output
|
||||||
|
independent test says TEST_VAR is 999
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'dependent test' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'dependent test' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'dependent test' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
dependent test
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'fail' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [DBUG] [_sproc] [ 'fail' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:08:56] [INFO] [_sproc] [ 'fail' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
This test will fail on purpose.
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'independent test 1' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'independent test 1' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
TEST OUTPUT: Test var is: 999
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'independent test 1' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'independent test 1' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'independent test 1' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
TEST OUTPUT: Test var is: 999
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'dependent test' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'dependent test' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'dependent test' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
dependent test
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'fail' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] DUP2: child_*_pipe[1]->STD*_FILENO
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] Attempt: Running as user 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] Attempt: Running as group_name 'bagira'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] UID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] GID of 'bagira' is '1000'.
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] Successfully set GID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [DBUG] [_sproc] [ 'fail' ] Successfully set UID to '1000' (bagira).
|
||||||
|
[2021-04-04_19:15:50] [INFO] [_sproc] [ 'fail' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
This test will fail on purpose.
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'independent test 1' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'independent test 1' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
TEST OUTPUT: Test var is: 999
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'independent test 2' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'independent test 2' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
independent test 2 output
|
||||||
|
independent test says TEST_VAR is 999
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'dependent test' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'dependent test' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
dependent test
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'fail' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:23:08] [INFO] [_sproc] [ 'fail' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
This test will fail on purpose.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'independent test 1' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'independent test 1' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
This is an independent test. It does not depend on other tests.
|
||||||
|
Environment file check: TEST_VAR from environment file is set to: 999
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'independent test 2' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'independent test 2' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
independent test 2 output
|
||||||
|
independent test says TEST_VAR is 999
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'dependent test' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'dependent test' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
dependent test
|
||||||
|
This test depends on another test having succeeded in order to execute.
|
||||||
|
This tests dependencies.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'fail' ] TEE Logging enabled.
|
||||||
|
[2021-04-04_19:45:01] [INFO] [_sproc] [ 'fail' ] Identity context set as user 'bagira' and group 'bagira'.
|
||||||
|
variables file says hello and set a variable named TEST_VAR
|
||||||
|
Failure handling test.
|
||||||
|
This test will fail on purpose.
|
||||||
@@ -1,36 +1,69 @@
|
|||||||
{
|
{
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "independent test 1",
|
"name": "independent test 1",
|
||||||
"target": "/usr/bin/true",
|
"target": "components/independent_test_1.bash",
|
||||||
"rectifier": "/usr/bin/true",
|
"rectifier": "",
|
||||||
"active": true,
|
"active": true,
|
||||||
"required": true,
|
"required": true,
|
||||||
"rectify": true
|
"log": true,
|
||||||
|
"user": "bagira",
|
||||||
|
"group": "bagira",
|
||||||
|
"rectify": false,
|
||||||
|
"shell": "/usr/bin/env bash",
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "independent test 2",
|
"name": "independent test 2",
|
||||||
"target": "/usr/bin/true",
|
"target": "components/independent_test_2.bash",
|
||||||
"rectifier": "/usr/bin/true",
|
"rectifier": "",
|
||||||
"active": true,
|
"active": true,
|
||||||
"required": false,
|
"required": true,
|
||||||
"rectify": false
|
"log": true,
|
||||||
|
"user": "bagira",
|
||||||
|
"group": "bagira",
|
||||||
|
"rectify": false,
|
||||||
|
"shell": "/usr/bin/env bash",
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "A DEFINITION THAT IS NOT USED",
|
"name": "dependent test",
|
||||||
"target": "/usr/bin/dialog --yesno test 50 50",
|
"target": "components/dependent_test.bash",
|
||||||
"rectifier": "/usr/bin/false",
|
"rectifier": "",
|
||||||
"active": false,
|
"active": true,
|
||||||
"required": false,
|
"required": true,
|
||||||
"rectify": true
|
"log": true,
|
||||||
|
"user": "bagira",
|
||||||
|
"group": "bagira",
|
||||||
|
"rectify": false,
|
||||||
|
"shell": "/usr/bin/env bash",
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dependent test",
|
"name": "curses dialog",
|
||||||
"target": "/usr/bin/false",
|
"target": "components/curses_dialog.bash",
|
||||||
"rectifier": "/usr/bin/true",
|
"rectifier": "",
|
||||||
"active": true,
|
"active": true,
|
||||||
"required": true,
|
"required": true,
|
||||||
"rectify": true
|
"log": false,
|
||||||
|
"user": "bagira",
|
||||||
|
"group": "bagira",
|
||||||
|
"rectify": false,
|
||||||
|
"shell": "/usr/bin/env bash",
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fail",
|
||||||
|
"target": "components/fail.bash",
|
||||||
|
"rectifier": "",
|
||||||
|
"active": true,
|
||||||
|
"required": false,
|
||||||
|
"log": true,
|
||||||
|
"user": "bagira",
|
||||||
|
"group": "bagira",
|
||||||
|
"rectify": false,
|
||||||
|
"shell": "/usr/bin/env bash",
|
||||||
|
"environment": "environments/rex.variables"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"units": [
|
|
||||||
{
|
|
||||||
"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/false",
|
|
||||||
"rectifier": "/usr/bin/true",
|
|
||||||
"active": true,
|
|
||||||
"required": true,
|
|
||||||
"rectify": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user