Compare commits
30 Commits
Examplar-1
...
Examplar-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a38de0c0 | ||
|
|
3517b9cc11 | ||
|
|
137c82ebaa | ||
|
|
e49cc335f3 | ||
|
|
4ea05b842a | ||
|
|
3bc60f56de | ||
|
|
88eaea7bfa | ||
|
|
d636ece26a | ||
|
|
839c3d398a | ||
|
|
11829aca39 | ||
|
|
8f6f7761c4 | ||
|
|
722688ebf3 | ||
|
|
3591bd344d | ||
|
|
1c354b4401 | ||
|
|
0db575a075 | ||
|
|
46e56b8d6f | ||
|
|
93c22887e8 | ||
|
|
9d89f5ad6a | ||
|
|
6fca4f2d91 | ||
|
|
c75fa9d2c8 | ||
|
|
49b4e77ca8 | ||
|
|
8a95bbf219 | ||
|
|
4d66a0f059 | ||
|
|
221edee07c | ||
|
|
0c70f7ee36 | ||
|
|
2b2225f3e4 | ||
|
|
6af1082852 | ||
|
|
9d5af160c5 | ||
|
|
da50f152f2 | ||
|
|
7404f07dc5 |
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(ftests)
|
||||
project(examplar)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++1z -O0 -DDEBUG=1")
|
||||
set(SOURCE_FILES examplar.cpp src/loaders/loaders.cpp src/loaders/loaders.h src/json/jsoncpp.cpp src/loaders/JSON_Loader.cpp src/loaders/JSON_Loader.h src/loaders/helpers.cpp src/loaders/helpers.h src/loaders/Suite.cpp src/loaders/Suite.h src/loaders/Plan.cpp src/loaders/Plan.h src/loaders/Conf.cpp src/loaders/Conf.h src/loaders/Unit.cpp src/loaders/Unit.h src/loaders/Task.cpp src/loaders/Task.h src/sproc/Sproc.cpp src/sproc/Sproc.h)
|
||||
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)
|
||||
|
||||
add_executable(ftests ${SOURCE_FILES})
|
||||
add_executable(examplar ${SOURCE_FILES})
|
||||
123
README.md
Normal file
123
README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Instructions
|
||||
These are instructions for using Examplar.
|
||||
|
||||
## Build
|
||||
|
||||
Compiling Examplar 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
|
||||
|
||||
1. Write a script that does a thing.
|
||||
2. Write a script that checks if that thing is done.
|
||||
3. Set up your check as a target in a unit.
|
||||
4. Set up your script as its rectifier.
|
||||
5. Turn on the rectify pattern in the unit definition.
|
||||
|
||||
## Definitions
|
||||
So you've got Examplar 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.
|
||||
|
||||
### Units
|
||||
|
||||
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 `rectifier`, which is the path to the automation script to be executed if the target call fails.
|
||||
* A `rectify` attribute, which tells Examplar whether or not to execute the rectifier in the case of failure when executing the target.
|
||||
* 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 `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.
|
||||
|
||||
### Tasks
|
||||
|
||||
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
|
||||
|
||||
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 Examplar.
|
||||
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.
|
||||
|
||||
### FILES
|
||||
There are several files used by Examplar.
|
||||
|
||||
#### CONFIG FILE and Attributes
|
||||
|
||||
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:
|
||||
|
||||
* `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.
|
||||
* `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`.
|
||||
|
||||
#### Configuration VERSION
|
||||
|
||||
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
|
||||
|
||||
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 Examplar pointing at that config file.
|
||||
Execute examplar:
|
||||
|
||||
examplar --verbose --config path/to/your/config/file.json
|
||||
|
||||
And you should see your 'hello world' script.
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"units_path": "/home/phanes/development/internal/Examplar/conf/units/all_test.units",
|
||||
"plan_path": "/home/phanes/development/internal/Examplar/conf/plans/test.plan",
|
||||
"config_version": "1"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "independent test 1",
|
||||
"target": "/usr/bin/true",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"rectify": true
|
||||
},
|
||||
{
|
||||
"name": "independent test 2",
|
||||
"target": "/usr/bin/true",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"rectify": false
|
||||
},
|
||||
{
|
||||
"name": "A DEFINITION THAT IS NOT USED",
|
||||
"target": "/usr/bin/dialog --yesno test 50 50",
|
||||
"rectifier": "/usr/bin/false",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"rectify": true
|
||||
},
|
||||
{
|
||||
"name": "dependent test",
|
||||
"target": "/usr/bin/false",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"rectify": true
|
||||
}
|
||||
]
|
||||
}
|
||||
145
examplar.cpp
145
examplar.cpp
@@ -19,45 +19,150 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "src/json/json.h"
|
||||
#include "src/loaders/loaders.h"
|
||||
#include "src/loaders/abstract/loaders.h"
|
||||
#include "src/Logger/Logger.h"
|
||||
#include "src/loaders/misc/helpers.h"
|
||||
|
||||
/*
|
||||
* TODO Logging -- Pump to syslog with clone to STDOUT
|
||||
* TODO Unit Files Directory instead of a single Unit File (optional to user)
|
||||
* TODO Commandline switches
|
||||
*/
|
||||
|
||||
|
||||
int main( )
|
||||
void print_usage()
|
||||
{
|
||||
bool verbose = true;
|
||||
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("/home/phanes/development/internal/Examplar/conf/config.json", verbose );
|
||||
Conf configuration = Conf(config_path, L_LEVEL );
|
||||
|
||||
// load the configuration file which contains filepaths to definitions of a plan and definitions of units.
|
||||
// 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;
|
||||
available_definitions.load_units_file( definitions_file, verbose );
|
||||
slog.log( E_DEBUG, "* Initialising suite (definition library).");
|
||||
Suite available_definitions = Suite( L_LEVEL );
|
||||
|
||||
Plan plan;
|
||||
plan.load_plan_file( plan_file, verbose );
|
||||
slog.log( E_INFO, "* Loading all actionable units into suite." );
|
||||
available_definitions.load_units_file( definitions_file );
|
||||
|
||||
plan.load_definitions( available_definitions, verbose );
|
||||
slog.log( E_DEBUG, "* Initialising plan." );
|
||||
Plan plan = Plan( &configuration, L_LEVEL );
|
||||
|
||||
std::cout << "Ready to execute all tasks in Plan." << std::endl;
|
||||
slog.log( E_INFO, "* Loading plan outline.");
|
||||
plan.load_plan_file( plan_file );
|
||||
|
||||
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( verbose );
|
||||
plan.execute();
|
||||
}
|
||||
|
||||
catch ( std::exception& e)
|
||||
{
|
||||
std::cerr << e.what() << std::endl;
|
||||
slog.log( E_FATAL, "Caught exception.");
|
||||
slog.log( E_FATAL, e.what() );
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
51
src/Logger/Logger.cpp
Normal file
51
src/Logger/Logger.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// Created by bagira on 6/13/20.
|
||||
//
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
Logger::Logger( int LOG_LEVEL, std::string mask )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
this->mask = mask;
|
||||
|
||||
setlogmask( LOG_UPTO( this->LOG_LEVEL ) );
|
||||
openlog( this->mask.c_str(), LOG_CONS | LOG_PID | LOG_NDELAY, LOG_PERROR | LOG_LOCAL1 );
|
||||
|
||||
}
|
||||
|
||||
void Logger::log( int LOG_LEVEL, std::string msg )
|
||||
{
|
||||
std::string ERR = "XXXX";
|
||||
|
||||
if ( LOG_LEVEL <= this->LOG_LEVEL )
|
||||
{
|
||||
switch ( LOG_LEVEL )
|
||||
{
|
||||
case E_DEBUG: ERR = "DBUG"; break;
|
||||
case E_FATAL: ERR = "FATL"; break;
|
||||
case E_INFO: ERR = "INFO"; break;
|
||||
case E_WARN: ERR = "WARN"; break;
|
||||
}
|
||||
|
||||
std::string s_msg = "[" + ERR + "] " + msg;
|
||||
syslog( this->LOG_LEVEL, s_msg.c_str() );
|
||||
|
||||
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;
|
||||
} else {
|
||||
std::cout << "[" << this->get_8601() << "]\t[" << this->mask << "]\t[" << ERR << "]\t" << 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();
|
||||
}
|
||||
35
src/Logger/Logger.h
Normal file
35
src/Logger/Logger.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by bagira on 6/13/20.
|
||||
//
|
||||
|
||||
#ifndef EXAMPLAR_LOGGER_H
|
||||
#define EXAMPLAR_LOGGER_H
|
||||
|
||||
#include <syslog.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
enum L_LVL {
|
||||
E_FATAL,
|
||||
E_WARN,
|
||||
E_INFO,
|
||||
E_DEBUG
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
Logger( int LOG_LEVEL, std::string mask );
|
||||
void log( int LOG_LEVEL, std::string msg );
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
std::string mask;
|
||||
std::string get_8601();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif //EXAMPLAR_LOGGER_H
|
||||
108
src/Sproc/Sproc.cpp
Normal file
108
src/Sproc/Sproc.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "Sproc.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include "../Logger/Logger.h"
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// Sproc::execute
|
||||
///
|
||||
/// \param input - The commandline input to execute.
|
||||
/// \return - The return code of the execution of input in the calling shell.
|
||||
int Sproc::execute(std::string run_as, std::string group, std::string command )
|
||||
{
|
||||
Logger slog = Logger( E_INFO, "_sproc" );
|
||||
|
||||
// the run_as_uid to capture the run_as_uid to run as
|
||||
int run_as_uid;
|
||||
int run_as_gid;
|
||||
|
||||
slog.log( E_DEBUG, "Attempt: Running as user '" + run_as + "'.");
|
||||
slog.log( E_DEBUG, "Attempt: Running as group '" + group + "'.");
|
||||
|
||||
if ( username_to_uid( run_as, run_as_uid ) )
|
||||
{
|
||||
slog.log( E_DEBUG, "UID of '" + run_as + "' is '" + std::to_string( run_as_uid ) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "Failed to look up UID for '" + run_as + "'.");
|
||||
return -404;
|
||||
}
|
||||
|
||||
if ( groupname_to_gid( group, run_as_gid ) )
|
||||
{
|
||||
slog.log( E_DEBUG, "GID of '" + group + "' is '" + std::to_string( run_as_gid ) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "Failed to look up DID for '" + group + "'.");
|
||||
return -404;
|
||||
}
|
||||
|
||||
// if you get this return value, it's an issue with this method and not your
|
||||
// called executable.
|
||||
int exit_code_raw = -666;
|
||||
|
||||
// fork a process
|
||||
int pid = fork();
|
||||
|
||||
if ( pid == 0 )
|
||||
{
|
||||
// child process
|
||||
if ( seteuid( run_as_uid ) == 0 )
|
||||
{
|
||||
slog.log( E_DEBUG, "Successfully set UID to '" + std::to_string(run_as_uid) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "Failed to set UID. Panicking." );
|
||||
return -401;
|
||||
}
|
||||
if ( setegid( run_as_gid ) == 0 )
|
||||
{
|
||||
slog.log( E_DEBUG, "Successfully set GID to '" + std::to_string(run_as_gid) + "'." );
|
||||
} else {
|
||||
slog.log( E_FATAL, "Failed to set GID. Panicking." );
|
||||
return -401;
|
||||
}
|
||||
exit_code_raw = system( command.c_str() );
|
||||
exit( WEXITSTATUS( exit_code_raw ) );
|
||||
} else if ( pid > 0 )
|
||||
{
|
||||
// parent process
|
||||
while ( ( pid = waitpid( pid, &exit_code_raw, 0 ) ) == -1 ) {}
|
||||
} else {
|
||||
// fork failed
|
||||
slog.log( E_FATAL, "Fork Failed");
|
||||
}
|
||||
return WEXITSTATUS( exit_code_raw );
|
||||
|
||||
|
||||
}
|
||||
@@ -23,13 +23,15 @@
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include "../Logger/Logger.h"
|
||||
|
||||
// executes a subprocess and captures STDOUT, STDERR, and return code.
|
||||
// should be able to recieve path of binary to be executed as well as any parameters
|
||||
class Sproc {
|
||||
public:
|
||||
// call the object. returnvalue is enum representing external execution attempt not binary exit code
|
||||
static int execute( std::string input );
|
||||
static int execute(std::string run_as, std::string group, std::string command );
|
||||
};
|
||||
|
||||
#endif //FTESTS_SPROC_H
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
#include "Conf.h"
|
||||
|
||||
/// CONF_PLANPATH_INVALID - Exception thrown when the Conf type can not load the supplied path for the Plan definition
|
||||
/// file.
|
||||
class CONF_PLANPATH_INVALID: public std::runtime_error { public:
|
||||
CONF_PLANPATH_INVALID(): std::runtime_error("conf: The supplied path for the plan definition file is invalid.") {}
|
||||
};
|
||||
|
||||
/// CONF_UNITSPATH_INVALID - Exception thrown when the Conf type can not load the supplied path for the Unit definition
|
||||
/// files.
|
||||
class CONF_UNITSPATH_INVALID: public std::runtime_error { public:
|
||||
CONF_UNITSPATH_INVALID(): std::runtime_error("conf: The supplied path for the unit definition file is invalid.") {}
|
||||
};
|
||||
|
||||
/// Conf::Conf - Constructor for Conf type. Loads the configuration for the application.
|
||||
/// TODO Expand to detect when a directory path is supplied for units_path or plan_path and import all Tasks and Units.
|
||||
///
|
||||
/// \param filename - The filename to load the configuration from.
|
||||
Conf::Conf( std::string filename, bool verbose ): JSON_Loader()
|
||||
{
|
||||
// load the conf file.
|
||||
this->load_json_file( filename, verbose );
|
||||
|
||||
// find the path to the plan file
|
||||
if (this->get_serialized(this->plan_path, "plan_path", true) != 0 ) { throw CONF_PLANPATH_INVALID(); }
|
||||
|
||||
// find the path to the unit definitions file
|
||||
if (this->get_serialized(this->units_path, "units_path", true) != 0 ) { throw CONF_UNITSPATH_INVALID(); }
|
||||
};
|
||||
|
||||
/// Conf::get_plan_path - Retrieves the path to the Plan definition file from the application configuration file.
|
||||
std::string Conf::get_plan_path() { return this->plan_path.asString(); }
|
||||
|
||||
/// Conf::get_units_path - Retrieves the path to the Unit definition file from the application configuration file.
|
||||
std::string Conf::get_units_path() { return this->units_path.asString(); }
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef FTESTS_CONF_H
|
||||
#define FTESTS_CONF_H
|
||||
#include "JSON_Loader.h"
|
||||
|
||||
class Conf: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
Json::Value plan_path;
|
||||
Json::Value units_path;
|
||||
|
||||
public:
|
||||
Conf( std::string filename, bool verbose );
|
||||
std::string get_plan_path();
|
||||
std::string get_units_path();
|
||||
};
|
||||
|
||||
#endif //FTESTS_CONF_H
|
||||
164
src/loaders/abstract/Conf.cpp
Normal file
164
src/loaders/abstract/Conf.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
#include "Conf.h"
|
||||
|
||||
/// ConfigLoadException - General exception handler for the Conf class.
|
||||
class ConfigLoadException: public std::exception
|
||||
{
|
||||
public:
|
||||
/** Constructor (C strings).
|
||||
* @param message C-style string error message.
|
||||
* The string contents are copied upon construction.
|
||||
* Hence, responsibility for deleting the char* lies
|
||||
* with the caller.
|
||||
*/
|
||||
explicit ConfigLoadException(const char* message):
|
||||
msg_(message)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor (C++ STL strings).
|
||||
* @param message The error message.
|
||||
*/
|
||||
explicit ConfigLoadException(const std::string& message):
|
||||
msg_(message)
|
||||
{}
|
||||
|
||||
/** Destructor.
|
||||
* Virtual to allow for subclassing.
|
||||
*/
|
||||
virtual ~ConfigLoadException() throw (){}
|
||||
|
||||
/** Returns a pointer to the (constant) error description.
|
||||
* @return A pointer to a const char*. The underlying memory
|
||||
* is in posession of the Exception object. Callers must
|
||||
* not attempt to free the memory.
|
||||
*/
|
||||
virtual const char* what() const throw (){
|
||||
return msg_.c_str();
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Error message.
|
||||
*/
|
||||
std::string msg_;
|
||||
};
|
||||
|
||||
|
||||
/// Conf::Conf - Constructor for Conf type. Loads the configuration for the application.
|
||||
/// TODO Expand to detect when a directory path is supplied for units_path or plan_path and import all Tasks and Units.
|
||||
///
|
||||
/// \param filename - The filename to load the configuration from.
|
||||
Conf::Conf( std::string filename, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_conf" )
|
||||
{
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
|
||||
// prepare context spaghetti
|
||||
this->override_context = false;
|
||||
|
||||
try {
|
||||
// load the test file.
|
||||
this->load_json_file( filename );
|
||||
}
|
||||
catch (std::exception) {
|
||||
this->slog.log( E_FATAL, "Unable to locate configuration file: '" + filename + "'." );
|
||||
throw ConfigLoadException("Config file not found.");
|
||||
}
|
||||
|
||||
if (this->get_serialized(this->config_version, "config_version" ) != 0)
|
||||
{
|
||||
throw ConfigLoadException("config_version string is not set in the config file supplied: " + filename);
|
||||
}
|
||||
if ( this->config_version.asString() != VERSION_STRING )
|
||||
{
|
||||
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
|
||||
if (this->get_serialized(this->units_path, "units_path" ) != 0 )
|
||||
{
|
||||
throw ConfigLoadException("units_path string is not set in the config file supplied: " + filename);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
this->override_context = true;
|
||||
}
|
||||
|
||||
if ( this->get_serialized(this->execution_context, "execution_context" ) != 0 )
|
||||
{
|
||||
throw ConfigLoadException("execution_context string is not set in the config file supplied: " + filename);
|
||||
} else {
|
||||
this->execution_context_literal = this->execution_context.asString();
|
||||
}
|
||||
|
||||
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.
|
||||
bool Conf::has_context_override() {
|
||||
return this->override_execution_context.asBool();
|
||||
}
|
||||
|
||||
/// Conf::get_execution_context - Specifies the path to the current working directory to set for all unit executions.
|
||||
std::string Conf::get_execution_context() {
|
||||
return this->execution_context_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.
|
||||
std::string Conf::get_units_path() { return this->units_path.asString(); }
|
||||
|
||||
/// Conf::set_execution_context- Sets the execution context.
|
||||
void Conf::set_execution_context( std::string execution_context )
|
||||
{
|
||||
this->execution_context_literal = execution_context;
|
||||
}
|
||||
|
||||
std::string Conf::get_env_vars_file()
|
||||
{
|
||||
return this->env_vars_file_literal;
|
||||
}
|
||||
70
src/loaders/abstract/Conf.h
Normal file
70
src/loaders/abstract/Conf.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Examplar - An automation and testing framework.
|
||||
|
||||
© SURRO INDUSTRIES and Chris Punches, 2017.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef FTESTS_CONF_H
|
||||
#define FTESTS_CONF_H
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include <exception>
|
||||
#include "../../Logger/Logger.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
#define STRINGIZE2(s) #s
|
||||
#define STRINGIZE(s) STRINGIZE2(s)
|
||||
# define IMPL_CONFIG_VERSION 3
|
||||
# define VERSION_STRING STRINGIZE(IMPL_CONFIG_VERSION)
|
||||
|
||||
class Conf: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
Json::Value plan_path;
|
||||
Json::Value units_path;
|
||||
Json::Value execution_context;
|
||||
Json::Value config_version;
|
||||
Json::Value env_vars_file;
|
||||
|
||||
std::string env_vars_file_literal;
|
||||
|
||||
// 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 false, Examplar should use the current working directory at time of execution
|
||||
Json::Value override_execution_context;
|
||||
|
||||
bool override_context;
|
||||
std::string execution_context_literal;
|
||||
|
||||
public:
|
||||
Conf( std::string filename, int LOG_LEVEL );
|
||||
|
||||
bool has_context_override();
|
||||
|
||||
std::string get_plan_path();
|
||||
std::string get_units_path();
|
||||
std::string get_execution_context();
|
||||
|
||||
void set_execution_context( std::string );
|
||||
|
||||
std::string get_env_vars_file();
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
|
||||
};
|
||||
|
||||
#endif //FTESTS_CONF_H
|
||||
@@ -120,37 +120,39 @@ protected:
|
||||
/// Plan::Plan() - Constructor for Plan class. A Plan is a managed container for a Task vector. These tasks reference
|
||||
/// Units that are defined in the Units files (Suite). If Units are definitions, Tasks are selections of those
|
||||
/// definitions to execute, and if Units together form a Suite, Tasks together form a Plan.
|
||||
Plan::Plan(): JSON_Loader() {};
|
||||
Plan::Plan( Conf * configuration, int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_plan" )
|
||||
{
|
||||
this->configuration = configuration;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// Plan::load_plan_file - Uses the json_root buffer on each run to append intact Units as they're deserialized from
|
||||
/// the provided file.
|
||||
///
|
||||
/// \param filename - The filename to load the plan from.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Plan::load_plan_file(std::string filename, bool verbose)
|
||||
void Plan::load_plan_file( std::string filename )
|
||||
{
|
||||
// plan always loads from file
|
||||
this->load_json_file( filename, verbose );
|
||||
this->load_json_file( filename );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer wih a json::value object in the supplied filename
|
||||
if ( this->get_serialized( jbuff, "plan", verbose ) == 0 )
|
||||
if ( this->get_serialized( jbuff, "plan" ) == 0 )
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->tasks vector
|
||||
// buffer for tasks to append:
|
||||
Task tmp_T;
|
||||
Task tmp_T = Task( this->LOG_LEVEL );
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
tmp_T.load_root( this->json_root[ index ], verbose );
|
||||
tmp_T.load_root( this->json_root[ index ] );
|
||||
this->tasks.push_back( tmp_T );
|
||||
if ( verbose ) {
|
||||
std::cout << "Added task \"" << tmp_T.get_name() << "\" to Plan." << std::endl;
|
||||
}
|
||||
this->slog.log( LOG_INFO, "Added task \"" + tmp_T.get_name() + "\" to Plan." );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,10 +176,10 @@ void Plan::get_task(Task & result, int index )
|
||||
///
|
||||
/// \param unit_definitions - The Suite to load definitions from.
|
||||
/// \param verbose - Whether to print verbose information to STDOUT.
|
||||
void Plan::load_definitions( Suite unit_definitions, bool verbose )
|
||||
void Plan::load_definitions( Suite unit_definitions )
|
||||
{
|
||||
// placeholder Unit
|
||||
Unit tmp_U;
|
||||
Unit tmp_U = Unit( this->LOG_LEVEL );
|
||||
|
||||
// for every task in the plan:
|
||||
for (int i = 0; i < this->tasks.size(); i++ )
|
||||
@@ -186,7 +188,7 @@ void Plan::load_definitions( Suite unit_definitions, bool verbose )
|
||||
unit_definitions.get_unit( tmp_U, this->tasks[i].get_name() );
|
||||
|
||||
// then have that task attach a copy of tmp_U
|
||||
this->tasks[i].load_definition( tmp_U, verbose );
|
||||
this->tasks[i].load_definition( tmp_U );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +212,7 @@ void Plan::get_task(Task & result, std::string provided_name )
|
||||
}
|
||||
if (! foundMatch )
|
||||
{
|
||||
std::cerr << "Task name \"" << provided_name << "\" was referenced but not defined!" << std::endl;
|
||||
this->slog.log( E_FATAL, "Task name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
throw Plan_InvalidTaskName();
|
||||
}
|
||||
}
|
||||
@@ -223,14 +225,14 @@ void Plan::get_task(Task & result, std::string provided_name )
|
||||
bool Plan::all_dependencies_complete(std::string name)
|
||||
{
|
||||
// get the task by name
|
||||
Task named_task;
|
||||
Task named_task = Task( this->LOG_LEVEL );
|
||||
this->get_task( named_task, name );
|
||||
|
||||
// get the dependencies of that task
|
||||
std::vector<std::string> deps = named_task.get_dependencies();
|
||||
|
||||
// create an empty task to assign values to during iteration
|
||||
Task tmpTask;
|
||||
Task tmpTask = Task( this->LOG_LEVEL );
|
||||
// iterate through its dependencies
|
||||
for ( int i = 0; i < deps.size(); i++ )
|
||||
{
|
||||
@@ -247,26 +249,26 @@ bool Plan::all_dependencies_complete(std::string name)
|
||||
/// Plan::execute() - Iterates through all tasks in a plan and executes them.
|
||||
///
|
||||
/// \param verbose
|
||||
void Plan::execute( bool verbose )
|
||||
void Plan::execute()
|
||||
{
|
||||
// for each task in this plan
|
||||
for ( int i = 0; i < this->tasks.size(); i++ )
|
||||
{
|
||||
if (this->all_dependencies_complete(this->tasks[i].get_name()) )
|
||||
{
|
||||
if ( verbose )
|
||||
{
|
||||
std::cout << "Executing task \"" << this->tasks[i].get_name() << "\"." << std::endl;
|
||||
}
|
||||
|
||||
this->slog.log( E_INFO, "Executing task \"" + this->tasks[i].get_name() + "\"." );
|
||||
try {
|
||||
this->tasks[i].execute( verbose );
|
||||
this->tasks[i].execute( this->configuration );
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
throw Plan_Task_GeneralExecutionException( "Plan Task: \"" + this->tasks[i].get_name() + "\" reported: " + e.what() );
|
||||
this->slog.log( E_FATAL, "Plan Task: \"" + this->tasks[i].get_name() + "\" reported: " + e.what() );
|
||||
throw Plan_Task_GeneralExecutionException("Could not execute task.");
|
||||
}
|
||||
} else {
|
||||
// not all deps met for this task
|
||||
throw Plan_Task_Missing_Dependency( "Plan Task \"" + this->tasks[i].get_name() + "\" was specified in the Plan but not executed due to missing dependencies. Please revise your plan." );
|
||||
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." );
|
||||
throw Plan_Task_Missing_Dependency( "Unmet dependency for task." );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,21 +22,24 @@
|
||||
#define FTESTS_PLAN_H
|
||||
|
||||
#include <string>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.h"
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Task.h"
|
||||
#include "Conf.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class Plan: public JSON_Loader
|
||||
{
|
||||
private:
|
||||
// storage for the tasks that make up the plan
|
||||
std::vector<Task> tasks;
|
||||
Conf * configuration;
|
||||
|
||||
public:
|
||||
Plan();
|
||||
Plan( Conf * configuration, int LOG_LEVEL );
|
||||
|
||||
// append this->tasks from JSON file
|
||||
void load_plan_file( std::string filename, bool verbose );
|
||||
void load_plan_file( std::string filename );
|
||||
|
||||
// fetch a task from this->tasks
|
||||
void get_task( Task & result, std::string provided_name );
|
||||
@@ -45,15 +48,19 @@ class Plan: public JSON_Loader
|
||||
void get_task( Task & result, int index );
|
||||
|
||||
// load unit definitions from a provided suite and import them into individual tasks
|
||||
void load_definitions( Suite unit_definitions, bool verbose );
|
||||
void load_definitions( Suite unit_definitions );
|
||||
|
||||
// fetch a corresponding Unit to a Task
|
||||
// void get_definition_from_task(Unit & result, Task input, bool verbose );
|
||||
|
||||
// execute all tasks in this plan
|
||||
void execute( bool verbose );
|
||||
void execute();
|
||||
|
||||
bool all_dependencies_complete(std::string name);
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //FTESTS_PLAN_H
|
||||
@@ -79,24 +79,13 @@ protected:
|
||||
/// human processes to allow modularly developed profiles of test suites. As inferred, Unit is expected to be one of
|
||||
/// the two types that are only instantiated once per application run, though it is designed to be used more than once
|
||||
/// if the implementor so desires.
|
||||
Suite::Suite(): JSON_Loader() {};
|
||||
|
||||
|
||||
bool is_file( std::string path)
|
||||
Suite::Suite( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_suite" )
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISREG(buf.st_mode);
|
||||
this->LOG_LEVEL;
|
||||
}
|
||||
|
||||
bool is_dir( std::string path )
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISDIR(buf.st_mode);
|
||||
}
|
||||
|
||||
void get_units_from_dir( std::vector<std::string> * files, std::string path )
|
||||
void Suite::get_units_from_dir( std::vector<std::string> * files, std::string path )
|
||||
{
|
||||
DIR* dirFile = opendir( path.c_str() );
|
||||
if ( dirFile )
|
||||
@@ -107,11 +96,20 @@ void get_units_from_dir( std::vector<std::string> * files, std::string path )
|
||||
errno = 0;
|
||||
while (( hFile = readdir( dirFile )) != NULL )
|
||||
{
|
||||
if ( !strcmp( hFile->d_name, "." )) continue;
|
||||
if ( !strcmp( hFile->d_name, ".." )) continue;
|
||||
if ( !strcmp( hFile->d_name, "." ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ( !strcmp( hFile->d_name, ".." ))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// hidden files
|
||||
if ( hFile->d_name[0] == '.' ) continue;
|
||||
if ( hFile->d_name[0] == '.' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// dirFile.name is the name of the file. Do whatever string comparison
|
||||
// you want here. Something like:
|
||||
@@ -123,18 +121,17 @@ void get_units_from_dir( std::vector<std::string> * files, std::string path )
|
||||
}
|
||||
closedir( dirFile );
|
||||
} else {
|
||||
std::cout << "file not found" << std::endl;
|
||||
this->slog.log( E_DEBUG, "File not found: " + path );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Suite::load_units_file - Uses the json_root buffer on each run to append intact Units as they're
|
||||
/// deserialized from the provided file.
|
||||
///
|
||||
/// \param units_path - The file to pull the JSON-formatted units from.
|
||||
/// \param verbose - Whether to print verbose output to STDOUT.
|
||||
void Suite::load_units_file( std::string units_path, bool verbose )
|
||||
void Suite::load_units_file( std::string units_path )
|
||||
{
|
||||
std::vector<std::string> unit_files;
|
||||
|
||||
@@ -149,28 +146,26 @@ void Suite::load_units_file( std::string units_path, bool verbose )
|
||||
unit_files.push_back( units_path );
|
||||
}
|
||||
|
||||
std::ostringstream infostring;
|
||||
infostring << "Unit files found: " << unit_files.size() << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cerr << infostring.str();
|
||||
this->slog.log( E_INFO, "Unit files found: " + std::to_string( unit_files.size() ) );
|
||||
|
||||
for ( int i = 0; i < unit_files.size(); i++ )
|
||||
{
|
||||
// will use json_root buffer on each run to append to this->units vector as valid units are found.
|
||||
this->load_json_file( unit_files[i], verbose );
|
||||
this->load_json_file( unit_files[i] );
|
||||
|
||||
// staging buffer
|
||||
Json::Value jbuff;
|
||||
|
||||
// fill the jbuff staging buffer with a json::value object in the supplied units_path
|
||||
if ( this->get_serialized( jbuff, "units", verbose ) == 0)
|
||||
if ( this->get_serialized( jbuff, "units" ) == 0)
|
||||
{
|
||||
this->json_root = jbuff;
|
||||
}
|
||||
|
||||
// iterate through the json::value members that have been loaded. append to this->units vector
|
||||
// buffer for units to append:
|
||||
Unit tmp_U;
|
||||
Unit tmp_U = Unit( this->LOG_LEVEL );
|
||||
|
||||
for ( int index = 0; index < this->json_root.size(); index++ )
|
||||
{
|
||||
// assemble the unit from json_root using the built-in value operator
|
||||
@@ -178,20 +173,13 @@ void Suite::load_units_file( std::string units_path, bool verbose )
|
||||
if ( tmp_U.get_active() ) {
|
||||
// append to this->units
|
||||
this->units.push_back( tmp_U );
|
||||
if ( verbose ) {
|
||||
std::ostringstream infostring;
|
||||
infostring << "Added unit \"" << tmp_U.get_name() << "\" to Suite." << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Added unit \"" + tmp_U.get_name() + "\" to Suite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// Suite::get_unit - returns a contained Unit identified by name attribute.
|
||||
///
|
||||
/// \param result - the unit type receiving the unit's value
|
||||
@@ -213,11 +201,8 @@ void Suite::get_unit(Unit & result, std::string provided_name)
|
||||
|
||||
if (! foundMatch )
|
||||
{
|
||||
std::ostringstream infostring;
|
||||
infostring << "Unit name \"" << provided_name << "\" was referenced but not defined!" << std::endl;
|
||||
syslog(LOG_ERR, infostring.str().c_str() );
|
||||
std::cerr << infostring.str();
|
||||
throw SuiteException( infostring.str() );
|
||||
this->slog.log( E_FATAL, "Unit name \"" + provided_name + "\" was referenced but not defined!" );
|
||||
throw SuiteException( "Undefined unit in use." );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#define FTESTS_UNITS_H
|
||||
|
||||
#include <vector>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.h"
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Unit.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
|
||||
class Suite: public JSON_Loader
|
||||
@@ -34,14 +36,22 @@ class Suite: public JSON_Loader
|
||||
std::vector<Unit> units;
|
||||
|
||||
public:
|
||||
// constructor, empty
|
||||
Suite();
|
||||
// constructor
|
||||
Suite( int LOG_LEVEL );
|
||||
|
||||
// load a unit definitions file and add valid unit definitions to this->units
|
||||
void load_units_file( std::string filename, bool verbose );
|
||||
void load_units_file( std::string filename );
|
||||
|
||||
// returns the unit identified by name
|
||||
void get_unit(Unit & result, std::string provided_name);
|
||||
|
||||
private:
|
||||
void get_units_from_dir( std::vector<std::string> * files, std::string path );
|
||||
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //FTESTS_UNITS_H
|
||||
@@ -19,10 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "Task.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <syslog.h>
|
||||
#include "../sproc/Sproc.h"
|
||||
|
||||
|
||||
/// Task_InvalidDataStructure - Exception thrown when a Task is defined with invalid JSON.
|
||||
class Task_InvalidDataStructure: public std::runtime_error {
|
||||
@@ -82,20 +79,24 @@ protected:
|
||||
|
||||
/// Task::Task() - Constructor for the Task class. The Task is the building block of a Plan indicating of which Unit to
|
||||
/// execute, and its dependencies on other units to have already been completed successfully.
|
||||
Task::Task()
|
||||
Task::Task( int LOG_LEVEL ):
|
||||
slog( LOG_LEVEL, "e_task" ),
|
||||
definition( LOG_LEVEL )
|
||||
{
|
||||
// it hasn't executed yet.
|
||||
this->complete = false;
|
||||
|
||||
// it hasn't been matched with a definition yet.
|
||||
this->defined = false;
|
||||
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// Task::load_root() - loads json values to private members
|
||||
///
|
||||
/// \param loader_root - the Json::Value to populate from.
|
||||
/// \param verbose - Whether to print verbose information to STDOUT.
|
||||
void Task::load_root(Json::Value loader_root, bool verbose )
|
||||
void Task::load_root(Json::Value loader_root )
|
||||
{
|
||||
if ( loader_root.isMember("name") ) {
|
||||
this->name = loader_root.get("name", "?").asString();
|
||||
@@ -110,16 +111,10 @@ void Task::load_root(Json::Value loader_root, bool verbose )
|
||||
// iterate through each member of that obj
|
||||
for ( int i = 0; i < des_dep_root.size(); i++ ) {
|
||||
// add each string to dependencies
|
||||
if ( des_dep_root[i].asString() != "" ) {
|
||||
if ( des_dep_root[i].asString() != "" )
|
||||
{
|
||||
this->dependencies.push_back( des_dep_root[i].asString() );
|
||||
if ( verbose ) {
|
||||
std::ostringstream infostring;
|
||||
infostring << "Added dependency \"" << des_dep_root[i].asString() << "\" to task \""
|
||||
<< this->get_name() << "\"." << std::endl;
|
||||
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
}
|
||||
this->slog.log( E_INFO, "Added dependency \"" + des_dep_root[i].asString() + "\" to task \"" + this->get_name() + "\"." );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,17 +129,10 @@ std::string Task::get_name()
|
||||
///
|
||||
/// \param selected_unit - The unit to attach.
|
||||
/// \param verbose - Whether to print to STDOUT.
|
||||
void Task::load_definition( Unit selected_unit, bool verbose )
|
||||
void Task::load_definition( Unit selected_unit )
|
||||
{
|
||||
this->definition = selected_unit;
|
||||
if ( verbose ) {
|
||||
std::ostringstream infostring;
|
||||
infostring << "Loaded definition \"" << selected_unit.get_name() << "\" for task \""
|
||||
<< this->get_name() << "\"." << std::endl;
|
||||
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
}
|
||||
this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan.");
|
||||
this->defined = true;
|
||||
}
|
||||
|
||||
@@ -174,15 +162,10 @@ bool Task::has_definition()
|
||||
return this->defined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Task::execute - execute a task's unit definition.
|
||||
/// See the design document for what flow control needs to look like here.
|
||||
/// \param verbose - Verbosity level - not implemented yet.
|
||||
void Task::execute( bool verbose )
|
||||
void Task::execute( Conf * configuration )
|
||||
{
|
||||
// DUFFING - If Examplar is broken it's probably going to be in this block.
|
||||
// Somebody come clean this up, eh?
|
||||
@@ -197,29 +180,35 @@ void Task::execute( bool verbose )
|
||||
|
||||
// get the name
|
||||
std::string task_name = this->definition.get_name();
|
||||
this->slog.log( E_DEBUG, "Using unit: \"" + task_name + "\"." );
|
||||
// END PREWORK
|
||||
|
||||
|
||||
// get the target execution command
|
||||
std::string target_command = this->definition.get_target();
|
||||
|
||||
// if we're in verbose mode, do some verbose things
|
||||
if ( verbose )
|
||||
// check if context override
|
||||
if ( configuration->has_context_override() )
|
||||
{
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tUsing unit \"" << task_name << "\"." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
|
||||
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tExecuting target \"" << target_command << "\"." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
// if so, set the CWD.
|
||||
chdir( configuration->get_execution_context().c_str() );
|
||||
this->slog.log( E_INFO, "Setting execution context: " + get_working_path() );
|
||||
}
|
||||
|
||||
|
||||
// a[0] execute target
|
||||
int return_code = Sproc::execute( target_command );
|
||||
// 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 + "\"." );
|
||||
if ( exists( target_command ) )
|
||||
{
|
||||
this->slog.log( E_DEBUG, "Executable exists.");
|
||||
} else {
|
||||
this->slog.log( E_FATAL, "Executable does not exist." );
|
||||
throw Task_NotReady();
|
||||
}
|
||||
this->slog.log( E_DEBUG, "Vars file: " + configuration->get_env_vars_file() );
|
||||
int return_code = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + target_command );
|
||||
|
||||
// **********************************************
|
||||
// d[0] Error Code Check
|
||||
@@ -227,13 +216,8 @@ void Task::execute( bool verbose )
|
||||
if ( return_code == 0 )
|
||||
{
|
||||
// d[0].0 ZERO
|
||||
this->slog.log( E_INFO, "Target \"" + task_name + "\" succeeded. Marking as complete." );
|
||||
|
||||
if ( verbose ) {
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tTarget " << task_name << " succeeded. Marking as complete." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
}
|
||||
this->mark_complete();
|
||||
|
||||
// a[1] NEXT
|
||||
@@ -243,12 +227,7 @@ void Task::execute( bool verbose )
|
||||
if ( return_code != 0 )
|
||||
{
|
||||
// d[0].1 NON-ZERO
|
||||
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tTarget \"" << task_name << "\" failed with exit code " << return_code << "." << std::endl;
|
||||
|
||||
syslog(LOG_ERR, infostring.str().c_str() );
|
||||
std::cerr << infostring.str();
|
||||
this->slog.log( E_WARN, "Target \"" + task_name + "\" failed with exit code " + std::to_string( return_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[1] Rectify Check
|
||||
@@ -264,18 +243,13 @@ void Task::execute( bool verbose )
|
||||
{
|
||||
// d[2].0 FALSE
|
||||
// a[2] NEXT
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this->definition.get_required() )
|
||||
{
|
||||
} else {
|
||||
// d[2].1 TRUE
|
||||
// a[3] EXCEPTION
|
||||
throw TaskException("Task \"" + task_name + "\" is required, and failed, and rectification is not enabled.");
|
||||
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, and failed, and rectification is not enabled." );
|
||||
throw TaskException( "Task failed: " + task_name );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[2] Required Check
|
||||
@@ -286,20 +260,14 @@ void Task::execute( bool verbose )
|
||||
if ( this->definition.get_rectify() )
|
||||
{
|
||||
// d[1].1 TRUE (Rectify Check)
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRectification pattern is enabled for \"" << task_name << "\"." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Rectification pattern is enabled for \"" + task_name + "\"." );
|
||||
|
||||
// a[4] Execute RECTIFIER
|
||||
std::string rectifier_command = this->definition.get_rectifier();
|
||||
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tExecuting rectification: " << rectifier_command << "." << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Executing rectification: " + rectifier_command + "." );
|
||||
|
||||
int rectifier_error = Sproc::execute( rectifier_command );
|
||||
int rectifier_error = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + rectifier_command );
|
||||
|
||||
// **********************************************
|
||||
// d[3] Error Code Check for Rectifier
|
||||
@@ -307,12 +275,7 @@ void Task::execute( bool verbose )
|
||||
if ( rectifier_error != 0 )
|
||||
{
|
||||
// d[3].1 Non-Zero
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRectification of \"" << task_name << "\" failed with exit code "
|
||||
<< rectifier_error << "." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_WARN, "Rectification of \"" + task_name + "\" failed with exit code " + std::to_string( rectifier_error ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[4] Required Check
|
||||
@@ -320,10 +283,7 @@ void Task::execute( bool verbose )
|
||||
if ( ! this->definition.get_required() ) {
|
||||
// d[4].0 FALSE
|
||||
// a[5] NEXT
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -331,7 +291,8 @@ void Task::execute( bool verbose )
|
||||
{
|
||||
// d[4].1 TRUE
|
||||
// a[6] EXCEPTION
|
||||
throw TaskException("Task \"" + task_name + "\" is required, and failed, then rectified but rectification failed.");
|
||||
this->slog.log( E_FATAL, "Task \"" + task_name + "\" is required, it failed, and then rectification failed. Lost cause." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[4] Required Check
|
||||
@@ -342,18 +303,12 @@ void Task::execute( bool verbose )
|
||||
if ( rectifier_error == 0 )
|
||||
{
|
||||
// d[3].0 Zero
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRectification returned successfully." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Rectification returned successfully." );
|
||||
|
||||
// a[7] Re-execute Target
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRe-Executing target \"" << this->definition.get_target() << "\"." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Re-Executing target \"" + this->definition.get_target() + "\"." );
|
||||
|
||||
int retry_code = Sproc::execute( target_command );
|
||||
int retry_code = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + target_command );
|
||||
|
||||
// **********************************************
|
||||
// d[5] Error Code Check
|
||||
@@ -362,20 +317,11 @@ void Task::execute( bool verbose )
|
||||
{
|
||||
// d[5].0 ZERO
|
||||
// a[8] NEXT
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRe-execution was successful." << std::endl;
|
||||
syslog( LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
this->slog.log( E_INFO, "Re-execution was successful." );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( retry_code != 0 )
|
||||
{
|
||||
} else {
|
||||
// d[5].1 NON-ZERO
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tRe-execution failed with exit code " << retry_code << "." << std::endl;
|
||||
syslog(LOG_ERR, infostring.str().c_str() );
|
||||
std::cerr << infostring.str();
|
||||
this->slog.log( E_WARN, "Re-execution failed with exit code " + std::to_string( retry_code ) + "." );
|
||||
|
||||
// **********************************************
|
||||
// d[6] Required Check
|
||||
@@ -384,11 +330,7 @@ void Task::execute( bool verbose )
|
||||
{
|
||||
// d[6].0 FALSE
|
||||
// a[9] NEXT
|
||||
infostring = std::ostringstream();
|
||||
infostring << "\tThis task is not required to continue the plan. Moving on." << std::endl;
|
||||
syslog(LOG_INFO, infostring.str().c_str() );
|
||||
std::cout << infostring.str();
|
||||
|
||||
this->slog.log( E_INFO, "This task is not required to continue the plan. Moving on." );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -396,7 +338,8 @@ void Task::execute( bool verbose )
|
||||
{
|
||||
// d[6].1 TRUE
|
||||
// a[10] EXCEPTION
|
||||
throw TaskException("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 \"" + task_name + "\" is required, and failed, then rectified but rectifier did not heal the condition causing the target to fail. Cannot proceed with Plan." );
|
||||
throw TaskException( "Lost cause, task failure." );
|
||||
}
|
||||
// **********************************************
|
||||
// end - d[6] Required Check
|
||||
@@ -21,9 +21,14 @@
|
||||
#ifndef FTESTS_TASK_H
|
||||
#define FTESTS_TASK_H
|
||||
#include <string>
|
||||
#include "../json/json.h"
|
||||
#include <unistd.h>
|
||||
#include "../../json/json.h"
|
||||
#include "Unit.h"
|
||||
#include "Suite.h"
|
||||
#include "Conf.h"
|
||||
#include <stdio.h>
|
||||
#include "../../Sproc/Sproc.h"
|
||||
#include "../misc/helpers.h"
|
||||
|
||||
class Task
|
||||
{
|
||||
@@ -47,13 +52,13 @@ class Task
|
||||
|
||||
public:
|
||||
// constructor
|
||||
Task();
|
||||
Task( int LOG_LEVEL );
|
||||
|
||||
// load a json::value into task members (second stage deserialization)
|
||||
void load_root( Json::Value loader_root, bool verbose );
|
||||
void load_root( Json::Value loader_root );
|
||||
|
||||
// appends definition unit as child member
|
||||
void load_definition( Unit definition, bool verbose );
|
||||
void load_definition( Unit definition );
|
||||
|
||||
bool is_complete();
|
||||
bool has_definition();
|
||||
@@ -62,12 +67,16 @@ class Task
|
||||
std::string get_name();
|
||||
|
||||
// execute this task's definition
|
||||
void execute( bool verbose );
|
||||
void execute( Conf * configuration );
|
||||
|
||||
void mark_complete();
|
||||
|
||||
// returns a pointer to the dependencies vector
|
||||
std::vector<std::string> get_dependencies();
|
||||
|
||||
private:
|
||||
Logger slog;
|
||||
int LOG_LEVEL;
|
||||
};
|
||||
|
||||
#endif //FTESTS_TASK_H
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
/// Unit_NotPopulated - Meant to be thrown when a Unit type is not populated before being used.
|
||||
/// Signaled by use of the 'populated' boolean member of the Unit class.
|
||||
@@ -30,6 +32,11 @@ class Unit_NotPopulated: public std::runtime_error { public:
|
||||
Unit_NotPopulated(): std::runtime_error("Unit: Attempted to access a member before loading values.") {}
|
||||
};
|
||||
|
||||
/// EnvironmentErrorFatal - Meant to be thrown when the environment is too broken for Examplar to do its job.
|
||||
class EnvironmentErrorFatal: public std::runtime_error { public:
|
||||
EnvironmentErrorFatal(): std::runtime_error("Unit: Environment is too broken to continue.") {}
|
||||
};
|
||||
|
||||
/// Unit_DataStructureException - Meant to be thrown when a Unit type is accessing a member that does not exist.
|
||||
class Unit_DataStructureException: public std::runtime_error { public:
|
||||
// TODO rework this to accept the key name being fetched
|
||||
@@ -48,7 +55,10 @@ 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
|
||||
/// target can run successfully.
|
||||
/// rectify, which is used as a flag to determine in the rectifier runs.
|
||||
Unit::Unit() {}
|
||||
Unit::Unit( int LOG_LEVEL ): JSON_Loader( LOG_LEVEL ), slog( LOG_LEVEL, "e_unit" )
|
||||
{
|
||||
this->LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// Unit::load_root - Takes a JSON::Value and assigns the members to the Unit being populated.
|
||||
///
|
||||
@@ -80,6 +90,44 @@ int Unit::load_root(Json::Value loader_root)
|
||||
if ( loader_root.isMember("rectify") )
|
||||
{ this->rectify = loader_root.get("rectify", errmsg).asBool(); } else throw Unit_DataStructureException();
|
||||
|
||||
// TODO functionize this
|
||||
char * lgn;
|
||||
std::string errmsg_user;
|
||||
|
||||
// if no user field is specified then default to the currently executing user
|
||||
if ( ( lgn = getlogin() ) == NULL )
|
||||
{
|
||||
throw EnvironmentErrorFatal();
|
||||
} else {
|
||||
errmsg_user = lgn;
|
||||
}
|
||||
// -TODO
|
||||
|
||||
|
||||
|
||||
if ( loader_root.isMember( "user" ) )
|
||||
{ this->user = loader_root.get( "user", errmsg_user ).asString(); } else this->user = lgn;
|
||||
|
||||
|
||||
// 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 EnvironmentErrorFatal();
|
||||
} 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;
|
||||
|
||||
this->populated = true;
|
||||
|
||||
return 0;
|
||||
@@ -92,7 +140,7 @@ int Unit::load_root(Json::Value loader_root)
|
||||
int Unit::load_string(std::string json_val)
|
||||
{
|
||||
// serialize
|
||||
this->load_json_string( json_val, true );
|
||||
this->load_json_string( json_val );
|
||||
|
||||
// deserialize
|
||||
this->load_root( this->json_root );
|
||||
@@ -162,3 +210,20 @@ bool Unit::get_rectify()
|
||||
if ( ! this->populated ) { throw Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
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 Unit_NotPopulated(); }
|
||||
return this->group;
|
||||
}
|
||||
@@ -26,8 +26,9 @@
|
||||
#ifndef FTESTS_UNIT_H
|
||||
#define FTESTS_UNIT_H
|
||||
#include <string>
|
||||
#include "../json/json.h"
|
||||
#include "JSON_Loader.h"
|
||||
#include "../../json/json.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class Unit: JSON_Loader
|
||||
{
|
||||
@@ -58,8 +59,16 @@ private:
|
||||
// if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required
|
||||
bool rectify;
|
||||
|
||||
// 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;
|
||||
|
||||
public:
|
||||
Unit();
|
||||
Unit( int LOG_LEVEL );
|
||||
|
||||
// loads a serialized jason::value object as a unit
|
||||
int load_root( Json::Value loader_root );
|
||||
@@ -75,6 +84,12 @@ public:
|
||||
bool get_active();
|
||||
bool get_required();
|
||||
bool get_rectify();
|
||||
std::string get_user();
|
||||
std::string get_group();
|
||||
|
||||
private:
|
||||
int LOG_LEVEL;
|
||||
Logger slog;
|
||||
};
|
||||
|
||||
#endif //FTESTS_UNIT_H
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef FTESTS_LOADERS_H
|
||||
#define FTESTS_LOADERS_H
|
||||
|
||||
#include "JSON_Loader.h"
|
||||
#include "../low_level/JSON_Loader.h"
|
||||
#include "Suite.h"
|
||||
#include "Plan.h"
|
||||
#include "Conf.h"
|
||||
@@ -19,8 +19,6 @@
|
||||
*/
|
||||
|
||||
#include "JSON_Loader.h"
|
||||
#include "helpers.h"
|
||||
#include <stdexcept>
|
||||
|
||||
/// JSON_Loader_NotReady - Exception thrown when a member function is called before data is populated.
|
||||
class JSON_Loader_NotReady: public std::runtime_error { public:
|
||||
@@ -40,51 +38,10 @@ 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.
|
||||
///
|
||||
/// The JSON_Loader type is a base type. It is meant to provide the functionalities shared between Suite and Plan.
|
||||
JSON_Loader::JSON_Loader()
|
||||
JSON_Loader::JSON_Loader( int LOG_LEVEL ): slog( LOG_LEVEL, "e_json" )
|
||||
{
|
||||
this->populated = false;
|
||||
}
|
||||
|
||||
/// JSON_Loader::load_json_file - Loads JSON from a filepath into a serialized representation assigned as a local member
|
||||
/// intended to be used as a buffer for further operations by base methods and derived class methods.
|
||||
///
|
||||
/// \param filename -
|
||||
/// \param verbose
|
||||
void JSON_Loader::load_json_file( std::string filename, bool verbose )
|
||||
{
|
||||
// reads from a file into a Json::Value type.
|
||||
Json::Reader json_reader;
|
||||
|
||||
// the a deserialized json type to contain what's read by the reader
|
||||
Json::Value json_root;
|
||||
|
||||
// first, check if the file exists
|
||||
if (! exists( filename ) )
|
||||
{
|
||||
std::cerr << "File '" << filename << "' does not exist." << std::endl;
|
||||
throw JSON_Loader_FileNotFound();
|
||||
}
|
||||
|
||||
// create the ifstream file handle
|
||||
std::ifstream json_file_ifstream( filename, std::ifstream::binary );
|
||||
|
||||
// use the reader to parse the ifstream to the local property
|
||||
bool parsingSuccessful = json_reader.parse( json_file_ifstream, this->json_root );
|
||||
|
||||
if (! parsingSuccessful )
|
||||
{
|
||||
std::cerr << "Failed to parse '" << filename << "':\n\t" << json_reader.getFormattedErrorMessages();
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
// if in verbose mode, give the user an "it worked" message
|
||||
if (verbose)
|
||||
{
|
||||
std::cout << "Parsed '" << filename << "' with " << this->json_root.size() << " element(s)." << std::endl;
|
||||
}
|
||||
}
|
||||
// Flag as ready for consumption.
|
||||
this->populated = true;
|
||||
this->LOG_LEVEL = LOG_LEVEL;
|
||||
}
|
||||
|
||||
/// JSON_Loader::load_json_string - loads json from std::string into a json::value type and sets to protected member
|
||||
@@ -92,7 +49,7 @@ void JSON_Loader::load_json_file( std::string filename, bool verbose )
|
||||
///
|
||||
/// \param input - The JSON-formatted string to serialize
|
||||
/// \param verbose - Whether or not to print verbose information to STDOUT.
|
||||
void JSON_Loader::load_json_string( std::string input, bool verbose )
|
||||
void JSON_Loader::load_json_string( std::string input )
|
||||
{
|
||||
// reads from a string into a Json::Value type.
|
||||
Json::Reader json_reader;
|
||||
@@ -108,18 +65,52 @@ void JSON_Loader::load_json_string( std::string input, bool verbose )
|
||||
|
||||
if (! parsingSuccessful )
|
||||
{
|
||||
std::cerr << "Failed to parse adhoc JSON value." << std::endl << input << std::endl << std::endl << json_reader.getFormattedErrorMessages();
|
||||
this->slog.log( E_FATAL, "Failed to parse adhoc JSON value: " + json_reader.getFormattedErrorMessages() );
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
this->slog.log( E_DEBUG, "Successfully parsed JSON string with " + std::to_string( this->json_root.size() ) + "elements. Value: '" + input + "'." );
|
||||
}
|
||||
// flag as ready for consumption
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
/// JSON_Loader::load_json_file - Loads JSON from a filepath into a serialized representation assigned as a local member
|
||||
/// intended to be used as a buffer for further operations by base methods and derived class methods.
|
||||
///
|
||||
/// \param filename -
|
||||
/// \param verbose
|
||||
void JSON_Loader::load_json_file( std::string filename )
|
||||
{
|
||||
// reads from a file into a Json::Value type.
|
||||
Json::Reader json_reader;
|
||||
|
||||
// the a deserialized json type to contain what's read by the reader
|
||||
Json::Value json_root;
|
||||
|
||||
// first, check if the file exists
|
||||
if (! exists( filename ) )
|
||||
{
|
||||
this->slog.log( E_FATAL, "File '" + filename + "' does not exist." );
|
||||
throw JSON_Loader_FileNotFound();
|
||||
}
|
||||
|
||||
// create the ifstream file handle
|
||||
std::ifstream json_file_ifstream( filename, std::ifstream::binary );
|
||||
|
||||
// use the reader to parse the ifstream to the local property
|
||||
bool parsingSuccessful = json_reader.parse( json_file_ifstream, this->json_root );
|
||||
|
||||
if (! parsingSuccessful )
|
||||
{
|
||||
this->slog.log( E_FATAL, "Failed to parse file '" + filename + "': " + json_reader.getFormattedErrorMessages() );
|
||||
throw JSON_Loader_InvalidJSON();
|
||||
|
||||
} else {
|
||||
// if in verbose mode, give the user an "it worked" message
|
||||
if ( verbose )
|
||||
{
|
||||
std::cout << "Successfully parsed JSON string with " << this->json_root.size() << " elements. Value:" << std::endl;
|
||||
std::cout << input << std::endl << std::endl;
|
||||
this->slog.log( E_DEBUG, "Parsed '" + filename + "' with " + std::to_string( this->json_root.size() ) + " element(s)." );
|
||||
}
|
||||
}
|
||||
// flag as ready for consumption
|
||||
// Flag as ready for consumption.
|
||||
this->populated = true;
|
||||
}
|
||||
|
||||
@@ -138,7 +129,7 @@ std::string JSON_Loader::as_string()
|
||||
/// \param key - The JSON key name to assign the value to (the root of the json::value object by name)
|
||||
/// \param verbose - Whether or not to print verbose output to STDOUT.
|
||||
/// \return - Boolean indicator of success or failure (0|1)
|
||||
int JSON_Loader::get_serialized(Json::Value &input, std::string key, bool verbose)
|
||||
int JSON_Loader::get_serialized(Json::Value &input, std::string key )
|
||||
{
|
||||
// throw if the class is not ready to be used.
|
||||
if ( ! this->populated ) { throw JSON_Loader_NotReady(); }
|
||||
@@ -151,11 +142,10 @@ int JSON_Loader::get_serialized(Json::Value &input, std::string key, bool verbos
|
||||
}
|
||||
|
||||
// key was not found
|
||||
if ( verbose )
|
||||
{
|
||||
|
||||
// verbose mode tells the user what key we were looking for.
|
||||
std::cerr << "Failed to find key '" << key << "'." << std::endl;
|
||||
}
|
||||
this->slog.log( E_FATAL, "Failed to find key '" + key + "'." );
|
||||
|
||||
// exit code for failure
|
||||
return 1;
|
||||
}
|
||||
@@ -19,11 +19,13 @@
|
||||
*/
|
||||
#ifndef FTESTS_JLOADER_H
|
||||
#define FTESTS_JLOADER_H
|
||||
#include "../json/json.h"
|
||||
#include "../../json/json.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <stdexcept>
|
||||
#include "../misc/helpers.h"
|
||||
#include "../../Logger/Logger.h"
|
||||
|
||||
class JSON_Loader
|
||||
{
|
||||
@@ -33,13 +35,13 @@ class JSON_Loader
|
||||
|
||||
public:
|
||||
// constructor
|
||||
JSON_Loader();
|
||||
JSON_Loader( int LOG_LEVEL );
|
||||
|
||||
// load from json file
|
||||
void load_json_file( std::string filename, bool verbose );
|
||||
void load_json_file( std::string filename );
|
||||
|
||||
// load from std::string json
|
||||
void load_json_string( std::string input, bool verbose );
|
||||
void load_json_string( std::string input );
|
||||
|
||||
// return as a JSONCPP serialized object
|
||||
// deprecated -- these aren't really used.
|
||||
@@ -47,6 +49,10 @@ class JSON_Loader
|
||||
std::string as_string();
|
||||
|
||||
// safely handle deserialized type retrieval (if we want it to be safe)
|
||||
int get_serialized(Json::Value &input, std::string key, bool verbose);
|
||||
int get_serialized(Json::Value &input, std::string key );
|
||||
|
||||
private:
|
||||
Logger slog;
|
||||
int LOG_LEVEL;
|
||||
};
|
||||
#endif //FTESTS_JLOADER_H
|
||||
@@ -25,3 +25,23 @@ bool exists(const std::string& name)
|
||||
struct stat buffer;
|
||||
return (stat (name.c_str(), &buffer) == 0);
|
||||
}
|
||||
|
||||
std::string get_working_path()
|
||||
{
|
||||
char temp[MAXPATHLEN];
|
||||
return ( getcwd(temp, MAXPATHLEN) ? std::string( temp ) : std::string("") );
|
||||
}
|
||||
|
||||
bool is_file( std::string path)
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISREG(buf.st_mode);
|
||||
}
|
||||
|
||||
bool is_dir( std::string path )
|
||||
{
|
||||
struct stat buf;
|
||||
stat( path.c_str(), &buf );
|
||||
return S_ISDIR(buf.st_mode);
|
||||
}
|
||||
@@ -22,9 +22,14 @@
|
||||
#define FTESTS_HELPERS_H
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
bool exists (const std::string& name);
|
||||
|
||||
|
||||
std::string get_working_path();
|
||||
bool is_file( std::string );
|
||||
bool is_dir( std::string );
|
||||
|
||||
#endif //FTESTS_HELPERS_H
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "Sproc.h"
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <wait.h>
|
||||
|
||||
/// Sproc::execute
|
||||
///
|
||||
/// \param input - The commandline input to execute.
|
||||
/// \return - The return code of the execution of input in the calling shell.
|
||||
int Sproc::execute(std::string input) {
|
||||
int child_exit_code = -666;
|
||||
child_exit_code = system( input.c_str() );
|
||||
child_exit_code = WEXITSTATUS( child_exit_code );
|
||||
return child_exit_code;
|
||||
}
|
||||
2
test/components/dependent_test.bash
Executable file
2
test/components/dependent_test.bash
Executable file
@@ -0,0 +1,2 @@
|
||||
echo "dependent test"
|
||||
exit $?
|
||||
11
test/components/independent_test_1.bash
Executable file
11
test/components/independent_test_1.bash
Executable file
@@ -0,0 +1,11 @@
|
||||
whoami
|
||||
id
|
||||
|
||||
touch /home/bagira/testfile
|
||||
|
||||
stat /home/bagira/testfile
|
||||
|
||||
#dialog --stdout --title "Interact with me!" \
|
||||
# --backtitle "This is user interaction." \
|
||||
# --yesno "Yes: pass, No: fail" 7 60
|
||||
exit $?
|
||||
2
test/components/independent_test_2.bash
Executable file
2
test/components/independent_test_2.bash
Executable file
@@ -0,0 +1,2 @@
|
||||
echo "independent test 2 output"
|
||||
exit $?
|
||||
8
test/config.json
Normal file
8
test/config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"execution_context_override": true,
|
||||
"execution_context": "/home/bagira/development/internal/examplar/test",
|
||||
"units_path": "units/",
|
||||
"plan_path": "plans/atomic.plan",
|
||||
"config_version": "3",
|
||||
"env_vars_file": "examplar.variables"
|
||||
}
|
||||
1
test/examplar.variables
Normal file
1
test/examplar.variables
Normal file
@@ -0,0 +1 @@
|
||||
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 ] }
|
||||
]
|
||||
}
|
||||
24
test/units/all_test.units
Normal file
24
test/units/all_test.units
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "independent test 1",
|
||||
"target": "components/independent_test_1.bash",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": true,
|
||||
"user": "root",
|
||||
"group": "root",
|
||||
"rectify": false
|
||||
},
|
||||
{
|
||||
"name": "independent test 2",
|
||||
"target": "components/independent_test_2.bash",
|
||||
"rectifier": "",
|
||||
"active": true,
|
||||
"required": false,
|
||||
"user": "bagira",
|
||||
"group": "bagira",
|
||||
"rectify": false
|
||||
}
|
||||
]
|
||||
}
|
||||
22
test/units/dependent_tests.units
Normal file
22
test/units/dependent_tests.units
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"units": [
|
||||
{
|
||||
"name": "A DEFINITION THAT IS NOT USED",
|
||||
"target": "/usr/bin/dialog --yesno test 50 50",
|
||||
"rectifier": "/usr/bin/false",
|
||||
"active": false,
|
||||
"required": true,
|
||||
"rectify": true,
|
||||
"user": "root"
|
||||
},
|
||||
{
|
||||
"name": "dependent test",
|
||||
"target": "/usr/bin/false",
|
||||
"rectifier": "/usr/bin/true",
|
||||
"active": false,
|
||||
"required": true,
|
||||
"rectify": true,
|
||||
"user": "root"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user