From eacd13d1c1b311abab850351a8a19274e342e759 Mon Sep 17 00:00:00 2001 From: gpoblon Date: Mon, 14 Sep 2020 16:33:28 +0200 Subject: [PATCH] Work in progress --- rudder-lang/docs/usage.adoc | 312 +++++++++++------- rudder-lang/src/bin/rudderc.rs | 56 ++-- rudder-lang/src/compile.rs | 82 +++-- rudder-lang/src/error.rs | 2 +- rudder-lang/src/generator.rs | 4 +- rudder-lang/src/generator/cfengine.rs | 34 +- rudder-lang/src/generator/dsc.rs | 39 +-- rudder-lang/src/generator/json.rs | 19 +- rudder-lang/src/generator/markdown.rs | 28 +- rudder-lang/src/io.rs | 227 +++++++------ rudder-lang/src/ir/value.rs | 70 ++-- rudder-lang/src/lib.rs | 20 +- rudder-lang/src/migrate.rs | 8 + rudder-lang/src/opt.rs | 220 +++++++----- rudder-lang/src/{logger.rs => output.rs} | 124 ++++--- rudder-lang/src/rudderlang_lib.rs | 6 +- rudder-lang/src/technique.rs | 39 ++- rudder-lang/src/technique/generate.rs | 6 + rudder-lang/src/technique/read.rs | 19 ++ .../tests/techniques/6.1.rc5/technique.rl | 2 +- .../tests/techniques/6.1.rc5/technique.rl.cf | 22 ++ .../techniques/6.1.rc5/technique.rl.json | 38 +++ .../tests/techniques/simplest/technique.json | 51 +-- .../tests/tmp/s_state_case/technique.rl | 41 --- rudder-lang/tests/utils/mod.rs | 10 +- rudder-lang/tools/rudderc-dev.conf | 19 +- rudder-lang/tools/rudderc-prod.conf | 23 +- 27 files changed, 913 insertions(+), 608 deletions(-) create mode 100644 rudder-lang/src/migrate.rs rename rudder-lang/src/{logger.rs => output.rs} (72%) create mode 100644 rudder-lang/src/technique/generate.rs create mode 100644 rudder-lang/src/technique/read.rs create mode 100644 rudder-lang/tests/techniques/6.1.rc5/technique.rl.cf create mode 100644 rudder-lang/tests/techniques/6.1.rc5/technique.rl.json delete mode 100644 rudder-lang/tests/tmp/s_state_case/technique.rl diff --git a/rudder-lang/docs/usage.adoc b/rudder-lang/docs/usage.adoc index edd0102ba37..0a21085ce8a 100644 --- a/rudder-lang/docs/usage.adoc +++ b/rudder-lang/docs/usage.adoc @@ -14,65 +14,157 @@ Optionally add rudderc to your path `export PATH=$PATH:/opt/rudder/bin/rudderc` === Usage -`rudderc` has 2 features: +`rudderc` has 4 features, called actions that will generate code. The action you need fully depend on the format you have and the output format you want: -* Compile which is the default behavior: takes a _RL_ technique as an input and generates the desired -* Translate which is the alternative mode: takes a _JSON_ technique as an input and generates a _rudder-lang_ technique (_.rl_) +* compile: generates either a DSC / CFEngine technique from a RudderLang technique +* migrate: generates a RudderLang technique from a CFEngine technique +* technique read: generates a JSON technique from a RudderLang technique +* technique generate: generates a JSON object that comes with RudderLang + DSC + CFEngine technique from a JSON technique. Please note that by exception, `--stdin` and `--stdout` options are default behavior for `technique generate`. -Any information log is displayed to `SDTOUT`, errors should be printed to `STDERR` +Any information log is displayed to `SDTOUT`, errors should be printed to `STDERR`. JSON log format handled. JSON output (including logs) is optional for compile and migrate but is default behavior for `technique read` and `generate`. [align=center] image::rudderc-simple.svg[rudderc abilities] .The CLI usage (`rudderc --help` or `rudderc -h` output slightly modified) ---- +Rudderc abilities are callable through subcommands, namely , , , , +allowing it to perform generation or translation from / into the following formats : [JSON, RudderLang, CFengine, DSC]. + +Run `rudderc --help` to access its inner options and flags helper + +Example: rudderc technique generate -c confs/my.conf -i techniques/technique.json -f rudderlang + USAGE: - rudderc [FLAGS] [OPTIONS] + rudderc + +FLAGS: + -h, --help + Prints help information + -V, --version + Prints version information + + +SUBCOMMANDS: + compile Generates either a DSC / CFEngine technique (`--format` option) from a RudderLang technique + help Prints this message or the help of the given subcommand(s) + migrate Generates a RudderLang technique from a CFEngine technique + technique A technique can either be used with one of the two following subcommands: `read` (from rudderlang + to json) or `generate` (from json to cfengine or dsc or rudderlang) +---- +_rudderc_ abilities / subcommands are called *actions*, that all share several flags and options +.Shared FLAGS / OPTIONS: +---- FLAGS: + -b, --backtrace Generates a backtrace in case an error occurs -h, --help Prints help information - -j, --json-log Use json logs instead of human readable output - -t, --translate Use technique translation mode rather than default compilation mode + --stdin Takes stdin as an input rather than using a file. Overwrites input file option + --stdout Takes stdout as an output rather than using a file. Overwrites output file option. Dismiss logs, including errors -V, --version Prints version information OPTIONS: - -c, --config-file - Path of the configuration file to use. - A configuration file is required (containing at least stdlib path) - [default: /opt/rudder/etc/rudderc.conf] + -c, --config-file Path of the configuration file to use. A configuration file is required + (containing at least stdlib and generic_methods paths) [default: + /opt/rudder/etc/rudderc.conf] + -i, --input Input file path. + If option path does not exist, concat config input with option. + -l, --log-level rudderc output logs verbosity [default: warn] [possible values: off, trace, + debug, info, warn, error] + -o, --output Output file path. + If option path does not exist, concat config output with option. Else base output on input. +---- - -f, --format - Enforce a compiler output format (overrides configuration format and manual technique extension) - [possible values: cf, dsc, json] +But some actions come with their own flags and options (listed below) on top of the previously mentioned: - -l, --log-level - rudderc output logs verbosity - [default: warn] - [possible values: off, trace, debug, info, warn, error] +.The first action, compile: +---- +Generates a JSON object that comes with RudderLang + DSC + CFEngine technique from a JSON technique - -s, --source - Input file path. Overwrites base input +USAGE: + rudderc compile [FLAGS] [OPTIONS] - -n, --technique-name - Technique name to use for both input (if no input provided) and output (if no output or provided), based on configuration file paths +FLAGS: + -j, --json-logs Use json logs instead of human readable output + This option will print a single JSON object that will contain logs, errors and generated data (or the file where it has been generated). + Whichever action is chosen, JSON output format is always the same. + However, some fields (data and destination file) could be set to `null`, make sure to handle `null`s properly + Note that NO_COLOR specs apply by default for json output. + Also note that setting NO_COLOR manually in your env will also work - -d, --dest - Output file path, overrides config and technique-name. - If neither an output nor a configuration file output path is set, source path is used +OPTIONS: + -f, --format Enforce a compiler output format (overrides configuration format). + [possible values: cf, cfengine, dsc, json] - -o, --output-technique-name - Technique name to use for output (if no output provided), based on configuration file path +... ---- +.The second action, migrate: +---- +Generates a RudderLang technique from a CFEngine technique + +USAGE: + rudderc migrate [FLAGS] [OPTIONS] + +FLAGS: + -j, --json-logs Use json logs instead of human readable output + This option will print a single JSON object that will contain logs, errors and generated data (or the file where it has been generated). + Whichever action is chosen, JSON output format is always the same. However, some fields (data and destination file) could be set to `null`, make sure to handle `null`s properly + Note that NO_COLOR specs apply by default for json output. + Also note that setting NO_COLOR manually in your env will also work +---- +.The third action, technique read (`read` is a subcommand of the `technique` subcommand): +---- +Generates a JSON technique from a RudderLang technique + +USAGE: + rudderc technique read [FLAGS] [OPTIONS] + +... +---- +.The fourth action, technique generate (`generate` is a subcommand of the `technique` subcommand): +---- +Generates a JSON object that comes with RudderLang + DSC + CFEngine technique from a JSON technique + +USAGE: + rudderc technique generate [FLAGS] [OPTIONS] + +... +---- + Most options are pretty straightforward but some explanations might help: * Flags and options must be written in `kebab-case` * A configuration file is required because _rudderc_ needs its own libraries to work (default path should point to an already working _Rudder_ configuration if _rudder agent_ was installed like previously suggested) -* Unlike the prevailing `--source` (`-s`) and `--dest` (`-d`) options, the `--technique-name ` (`-n`) and `--technique-output-name ` (`-o`) options makes use of the configuration file input and output paths and concatenates the `` to these -* `--source` > configuration source + `--technique-name` > configuration source if it is a file -* `--dest` > configuration dest + `--technique-name` (`-n`) > configuration dest if it is a file > `--source` as destination with updated format -* `--format` (`-f`) > configuration format > `--dest` technique extension -* Log levels are ordered (trace > debug > info > warn > error) +* Configuration can define flags and options but CLI will always overwrite config defined ones. ie: CLI `--output` > config `output` +* `--stdin` > `--input` +* `--stdout` > --output > `input` as destination with updated extension +* `--format` > `--output` technique extension +* `--log-levels` are ordered (trace > debug > info > warn > error) which means `info` includes `warn` and `error` +* `--stdin` is designed to work with pipes (ex: `cat file.rl` | rudderc compile -c file.conf -f cf`), it won't wait for an input. Higher priority than `--input` option +* `--stdout` will dismiss any kind of logs, including errors. Only thing that will be printed to terminal is the expected result. If empty, try again with a log, there is an error. Higher priority than `--output` option + +==== Options: how are input, output and format dealt with: + +Internally for input the compiler looks for an existing file until it founds one, in the following order: +* solely from the CLI input option +* join configuration action input as dir + CLI input option +* solely from the configuration action input (if the file exists) +* if none worked, error + +Internally for output, the compiler looks for an existing path to write a file on, until it founds one: +* solely from the CLI output option +* join configuration action output as dir + CLI output option +* solely from the configuration action output +* uses input and only updates the extension +* if none worked, error + +Internally for format when required (`compile`): +* for any action but `compile`, format is set by the program +* compile action: explicit CLI `--format` option. Note that values are limited. +* compile action: output file extension is used +* if none worked, error + ==== Configuration file @@ -84,75 +176,70 @@ Entire _rudder-lang_ environment is already set up alongside the agent: this inc [source,toml] ---- [shared] - stdlib="libs/" # only required field for rudderc --compile - ncf_generic_methods="/usr/share/ncf/tree/30_generic_methods/" - dsc_generic_methods="/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/" +stdlib="libs/" +cfengine_methods="repos/ncf/tree/30_generic_methods/" +alt_cfengine_methods="repos/dsc/plugin/ncf/30_generic_methods/" +dsc_methods="repos/dsc/packaging/Files/share/initial-policy/ncf/30_generic_methods/" [compile] - source="./tests/test_files/tester/" - dest="./tests/test_files/tester/" - # format="dsc" +input="tests/techniques/simplest/technique.rl" +output="tests/techniques/simplest/technique.rl.cf" -[translate] - source="tests/test_files/tester/" - dest="tests/test_files/tester/" - # format="dsc" # format here will just be ignored as translate can only generate rudder-lang +[migrate] +input="tests/techniques/simplest/technique.cf" +output="tests/techniques/simplest/technique.cf.rl" -[testing_loop] - cfengine="/opt/rudder/bin/cf-promises" - ncf_tools="/usr/share/ncf/tools/" - py_modules="tools/" ----- +[technique_read] +input="tests/techniques/simplest/technique.rl" +output="tests/techniques/simplest/technique.rl.json" -The configuration file can be used to shorten arguments. +[technique_generate] +input="tests/techniques/simplest/technique.json" +output="tests/techniques/simplest/technique_array.json" -The `source` and `dest` fields from both `[translate]` and `[compile]` can either be filled with a path or a file. +[testing_loop] +cfengine="/opt/rudder/bin/cf-promises" +ncf_tools="repos/ncf/tools/" +py_modules="tools/" -===== Configuration file set with directories -[source,toml] ---- -... - -[compile] - source="rl/" - dest="cf/" ----- - -Paths will respectively be prepended to _rudderc_ `-n` and `-o` options - -===== Configuration file set with files -Paths will be used as they are, since they already define a single technique. +The configuration file can be used to shorten arguments. -Note that `-n` and `-o` _rudderc_ options will use the config specified directories and only override technique name +There is a table for each action (`compile`, `technique_read`, `technique_generate`, `migrate`), that can hold their own two limited fields: `input` and `output`. +Meaningful usage is that these two fields are paths that are completed by CLI filenames: `--input ` / `--output ` CLI options. +In other words: config options are paths (directories), to which is joined the cli option. +But configure it using a file and not use the CLI options will work. ==== Compilation example 1. Required: a config file to work on a local environment: -.tools/myconf +.tools/my.conf [source,toml] ---- [shared] - stdlib="libs/" # only required field for rudderc --compile +stdlib="libs/" +cfengine_methods="repos/ncf/tree/30_generic_methods/" +alt_cfengine_methods="repos/dsc/plugin/ncf/30_generic_methods/" +dsc_methods="repos/dsc/packaging/Files/share/initial-policy/ncf/30_generic_methods/" ---- 2. CLI full version ---- -rudderc --json-log --log-level debug --config-file tools/myconf --source rl/technique.rl --dest dsc/technique.rl.dsc --format dsc +rudderc compile --json-log --log-level debug --config-file tools/my.conf --input tests/techniques/technique.rl --output tests/techniques/technique.rl.dsc --format dsc ---- 3. CLI shortened version ---- -rudderc -j -l debug -c tools/myconf -n technique.rl -o technique.rl.dsc -f dsc +rudderc compile -j -l debug -c tools/my.conf -i tests/techniques/technique.rl -f dsc ---- What it means: -* Compile is the default behavior, therefore it has no dedicated option. -* Compiles `./rl/technique.rl` (`-s`) into `./dsc/technique.rl.dsc` (`-d`), -* Use the configuration file located at `./tools/myconf` (`-c`), +* Compiles `tests/techniques/technique.rl` (`-i`) into `tests/techniques/technique.rl.dsc` (output based on input), +* Use the configuration file located at `./tools/my.conf` (`-c`), * Output technique format is DSC (`--format`). Note that this parameter is optional since `-d` defines the right technique format by its extension * Output log format is JSON (`-j`), * The following log levels: error, warn, info, debug will be printed to the terminal @@ -168,71 +255,70 @@ By using an adapted configuration file, it can be simplified: stdlib="libs/" # only required field for rudderc [compile] - source="rl/technique.rl" - dest="dsc/technique.rl.dsc" - format="dsc" + input="tests/techniques/" + output="tests/techniques/" ---- Lightest compilation using CLI. ---- -rudderc -j -l debug -c tools/myconf ----- - -Or using `rl` and `dsc` directories with other techniques: +rudderc -j -l debug -c tools/myconf -i technique.rl ---- -rudderc -j -l debug -c tools/myconf -n another.rl -o another_from_rl.rl.dsc ----- -==== Translation example +Input will be a concatenation of config and cli: `tests/techniques/technique.rl`. Output still based on input. + +5. config + CLI shortest version + +By using an adapted configuration file, it can be simplified: -1. Required: a config file to work on a local environment: .tools/myconf [source,toml] ---- [shared] - stdlib="libs/" # only required field for rudderc --compile ----- + stdlib="libs/" # only required field for rudderc -2. CLI full version ----- -rudderc --json-log --log-level debug --config-file tools/myconf --translate --source json/technique.json --dest rl/from_json.rl +[compile] + input="rl/technique.rl" + output="dsc/technique.rl.dsc" ---- -3. CLI shortened version +Lightest compilation using CLI. ---- -rudderc -tj -l debug -c tools/myconf -s technique.json -d rl/from_json.rl +rudderc -j -l debug -c tools/myconf ---- -What it means: -- Translate (`-t`) `./json/technique.json` (`-s`) into `./rl/from_json.rl` (`-d`), -- Use the configuration file located at `./tools/myconf` (`-c`), -- Output log format is JSON (`-j`), -- The following log levels: error, warn will be printed to the terminal - -4. CLI + config lightened version +==== JSON Output -By using an adapted configuration file, it can be simplified: +If you decided to go with the `--json-output` option, it means output will consist of a single JSON object: -.tools/myconf -[source,toml] +.STDOUT +[source,json] ---- -[shared] - stdlib="libs/" # only required field for rudderc - -[translate] - source="json/technique.json" - dest="rl/from_json.rl" ----- - -Lightest translation using CLI. +{ + "action": "compile", + "time": "1600331631367", + "status": "success", + "source": "tests/techniques/simplest/technique.rl", + "logs": [], + "data": [ + { + "format": "DSC", + "destination": "tests/techniques/6.1.rc5/technique.dsc", + "content": null + } + ], + "errors": [] +} ---- -rudderc -tj -l warn -c tools/myconf ----- -Or using `json` and `rl` directories with other techniques: ----- -rudderc -tj -l warn -c tools/myconf -n another.json -o another_from_json.rl ----- +* Output always use the same squeleton which is the one you just read. +* `data` field: +** Length always 0 in case of error # TODO check for technique generate +** Length always 3 when `technique generate called` +** Length always 1 in any other case since other actions only generate 1 format +* `content` field is null if its content has succesfully been written to a file +* `destination` field is null if content is directly written in the JSON +* `errors` field is an array of strings +# TODO log field == Using the Technique Editor diff --git a/rudder-lang/src/bin/rudderc.rs b/rudder-lang/src/bin/rudderc.rs index f49f32a1f9d..e7838c7d827 100644 --- a/rudder-lang/src/bin/rudderc.rs +++ b/rudder-lang/src/bin/rudderc.rs @@ -14,19 +14,18 @@ //! //! 3- ncf library -> generate_lib() -> resourcelib.rl + translate-config -use colored::Colorize; // imports log macros; use log::*; use std::process::exit; use structopt::StructOpt; use rudderc::{ - compile::compile_file, + compile::compile, error::Error, migrate::migrate, opt::Opt, - technique::{generate_technique, read_technique}, - Action, ActionResult, + technique::{technique_generate, technique_read}, + Action, }; // MAIN @@ -66,62 +65,51 @@ use rudderc::{ fn main() { let command = Opt::from_args(); let (output, log_level, has_backtrace) = command.extract_logging_infos(); - let action = command.to_action(); - // Initialize logger + let action = command.as_action(); + // Initialize logger and output output.init(log_level, action, has_backtrace); - let ctx = command.extract_parameters().unwrap_or_else(|e| { error!("{}", e); // required before returning in order to have proper logging output.print( action, - "input not set", - Err(Error::new("Not set yet".to_owned())), + "Input not set".to_owned(), + Err(Error::new( + "Could not determine proper I/O from given parameters".to_owned(), + )), ); exit(1); }); - info!("I/O context: {}", ctx); // Actual action // read = rl -> json // migrate = cf -> rl // compile = rl -> cf / dsc - // generate = json -> rl / cf / dsc - // TODO make the right calls + // generate = json -> json { rl + cf + dsc } // TODO collect logs in ram rather than directly printing it let action_result = match action { - Action::Compile => compile_file(&ctx, true), + Action::Compile => compile(&ctx, true), // TODO Migrate: call cf_to_json perl script then call json->rl == Technique generate() - Action::Migrate => unimplemented!(), - // TODO: rl -> json + add a json wrapper : { data: {}, errors: {}} - Action::ReadTechnique => unimplemented!(), + Action::Migrate => migrate(&ctx), + Action::ReadTechnique => technique_read(&ctx), // TODO Generate: call technique generate then compile into all formats + json wrapper: { rl: "", dsc: "", cf: "", errors:{} } - Action::GenerateTechnique => unimplemented!(), + Action::GenerateTechnique => technique_generate(&ctx), }; - // these logs should disappear since output will take care of it (to only print json or in terminal, not a mix) - // match &action_result { - // Ok(_) => info!( - // "{} {}", - // format!("{:?}", action).bright_green(), - // "OK".bright_cyan() - // ), - // Err(e) => error!("{}", e), - // }; let action_status = action_result.is_ok(); - output.print(action, ctx.input.display(), action_result); + output.print( + action, + match &ctx.input { + Some(path) => path.to_string_lossy().to_string(), + None => "STDIN".to_owned(), + }, + action_result, + ); if action_status { exit(1) } } -// prévu refonte cli + tests dsc -// en pratique la refonte cli a soulevé pas mal de questions: -// passer de : créer un fichier dans un format particulier dans un fichier -// à : 4 actions possibles, peut generer +rs formats dans un json wrappé, qui contient possiblement les logs (qui sont mal formattés actuellement), ou des fichiers directement etc -// -> impl action system -// lié à çá le souci de logs - // Phase 2 // - function, measure(=fact), action // - variable = anything diff --git a/rudder-lang/src/compile.rs b/rudder-lang/src/compile.rs index dfd7757d5c9..4b7728612d3 100644 --- a/rudder-lang/src/compile.rs +++ b/rudder-lang/src/compile.rs @@ -12,30 +12,54 @@ use crate::{ ActionResult, }; use colored::Colorize; -use std::{fs, path::Path}; +use std::{ + fs, + io::Read, + path::{Path, PathBuf}, +}; use typed_arena::Arena; /// Add a single file content to the sources and parse it pub fn parse_file<'src>( past: &mut PAST<'src>, sources: &'src Arena, - path: &Path, + path: &Option, ) -> Result<()> { - let filename = sources.alloc(match path.file_name() { - Some(file) => file.to_string_lossy().to_string(), - None => return Err(Error::new(format!("{:?} should be a .rl file", path))), - }); - info!( - "|- {} {}", - "Parsing".bright_green(), - filename.bright_yellow() - ); - match fs::read_to_string(path) { - Ok(content) => { - let content_str = sources.alloc(content); - past.add_file(filename, content_str) + match path { + Some(file_path) => { + let filename = sources.alloc(match file_path.file_name() { + Some(file) => file.to_string_lossy().into(), + None => return Err(Error::new(format!("{:?} should be a .rl file", path))), + }); + info!( + "|- {} {}", + "Parsing".bright_green(), + filename.bright_yellow() + ); + match fs::read_to_string(file_path) { + Ok(content) => { + let content_str = sources.alloc(content); + past.add_file(filename, content_str) + } + Err(e) => Err(err!(Token::new(filename, ""), "{}", e)), + } + } + None => { + sources.alloc("STDIN".to_owned()); + info!( + "|- {} {}", + "Parsing".bright_green(), + "STDIN".bright_yellow() + ); + let mut buffer = String::new(); + match std::io::stdin().read_to_string(&mut buffer) { + Ok(content) => { + let content_str = sources.alloc(buffer); + past.add_file("STDIN", content_str) + } + Err(e) => Err(err!(Token::new("STDIN", ""), "{}", e)), + } } - Err(e) => Err(err!(Token::new(filename, ""), "{}", e)), } } @@ -48,10 +72,13 @@ pub fn technique_to_ir<'src>( // read and add files info!( - "{} of {} into {}", + "{} of {}", "Processing compilation".bright_green(), - ctx.input.to_string_lossy().bright_yellow(), - ctx.output.to_string_lossy().bright_yellow() + match &ctx.input { + Some(input) => input.to_string_lossy(), + None => "STDIN".into(), + } + .bright_yellow(), ); parse_file(&mut past, &sources, &ctx.input)?; @@ -68,20 +95,27 @@ pub fn technique_to_ir<'src>( } /// Compile a file from rudder-lang to cfengine / dsc / json -pub fn compile_file(ctx: &IOContext, technique: bool) -> Result> { +pub fn compile(ctx: &IOContext, technique: bool) -> Result> { let sources = Arena::new(); let ir = technique_to_ir(ctx, &sources)?; // generate final output info!("|- {}", "Generating output code".bright_green()); + let input = match &ctx.input { + Some(i) => Some(i.as_path()), + None => None, + }; + let output = match &ctx.output { + Some(o) => Some(o.as_path()), + None => None, + }; + let (input_file, output_file) = if technique { // TODO this should be a technique name not a file name - (Some(ctx.input.as_path()), Some(ctx.output.as_path())) + (input, output) } else { (None, None) }; let mut generator = new_generator(&ctx.format)?; - generator.generate(&ir, input_file, output_file, technique)?; - // TODO fill action result directly - Ok(vec![ActionResult::default()]) + generator.generate(&ir, input_file, output_file, technique) } diff --git a/rudder-lang/src/error.rs b/rudder-lang/src/error.rs index 57a75a2cf4b..3ba48e99198 100644 --- a/rudder-lang/src/error.rs +++ b/rudder-lang/src/error.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2019-2020 Normation SAS -use crate::logger::Backtrace; /// We write our own error type to have a consistent error type through all our code. /// We translate other types to this one when necessary. /// All case contain 4 elements: @@ -12,6 +11,7 @@ use crate::logger::Backtrace; /// - Compilation error: usually we can skip what we are doing and go to next iteration /// - List: aggregate compilation errors so that user can fix them all ant once /// +use crate::output::Backtrace; use crate::parser::Token; use colored::Colorize; use ngrammatic::CorpusBuilder; diff --git a/rudder-lang/src/generator.rs b/rudder-lang/src/generator.rs index d57d9e6b3f1..b39622bf628 100644 --- a/rudder-lang/src/generator.rs +++ b/rudder-lang/src/generator.rs @@ -7,7 +7,7 @@ pub mod json; pub mod markdown; pub use self::{cfengine::CFEngine, dsc::DSC, json::JSON, markdown::Markdown}; -use crate::{error::*, ir::ir2::IR2}; +use crate::{error::*, ir::ir2::IR2, ActionResult}; use serde::{Deserialize, Deserializer, Serialize}; use std::{fmt, path::Path, str::FromStr}; @@ -22,7 +22,7 @@ pub trait Generator { source_file: Option<&Path>, dest_file: Option<&Path>, technique_metadata: bool, - ) -> Result<()>; + ) -> Result>; } pub fn new_generator(format: &Format) -> Result> { diff --git a/rudder-lang/src/generator/cfengine.rs b/rudder-lang/src/generator/cfengine.rs index 544aea8a979..a8e42791798 100644 --- a/rudder-lang/src/generator/cfengine.rs +++ b/rudder-lang/src/generator/cfengine.rs @@ -7,8 +7,9 @@ use crate::{ generator::cfengine::syntax::{quoted, Bundle, Method, Policy, Promise, MAX_INT, MIN_INT}, ir::{enums::EnumExpressionPart, ir2::IR2, resource::*, value::*}, parser::*, + ActionResult, Format, }; -use std::{collections::HashMap, ffi::OsStr, fs::File, io::Write, path::Path}; +use std::{collections::HashMap, ffi::OsStr, path::Path}; use toml::Value as TomlValue; mod syntax; @@ -286,8 +287,8 @@ impl Generator for CFEngine { source_file: Option<&Path>, dest_file: Option<&Path>, policy_metadata: bool, - ) -> Result<()> { - let mut files: HashMap = HashMap::new(); + ) -> Result> { + let mut files: Vec = Vec::new(); // TODO add global variable definitions for (resource_name, resource) in gc.resources.iter() { for (state_name, state) in resource.states.iter() { @@ -351,27 +352,22 @@ impl Generator for CFEngine { .name(extract("name")) .version(extract("version")) .bundle(bundle); - files.insert(file_to_create, policy.to_string()); + files.push(ActionResult::new( + Format::CFEngine, + Some(file_to_create.into()), + Some(policy.to_string()), + )); } else { - files.insert(file_to_create, bundle.to_string()); + files.push(ActionResult::new( + Format::CFEngine, + Some(file_to_create.into()), + Some(bundle.to_string()), + )); } } } - // create file if needed - if files.is_empty() { - match dest_file { - Some(filename) => File::create(filename).expect("Could not create output file"), - None => return Err(Error::new("No file to create".to_owned())), - }; - } - // write to file - for (name, content) in files.iter() { - let mut file = File::create(name).expect("Could not create output file"); - file.write_all(content.as_bytes()) - .expect("Could not write content into output file"); - } - Ok(()) + Ok(files) } } diff --git a/rudder-lang/src/generator/dsc.rs b/rudder-lang/src/generator/dsc.rs index 0fae58db035..79a64af15a2 100644 --- a/rudder-lang/src/generator/dsc.rs +++ b/rudder-lang/src/generator/dsc.rs @@ -5,9 +5,10 @@ use super::Generator; use crate::{ ir::{enums::EnumExpressionPart, ir2::IR2, resource::*, value::*}, parser::*, + ActionResult, Format, }; -use std::{collections::HashMap, ffi::OsStr, fs::File, io::Write, path::Path}; +use std::{collections::HashMap, ffi::OsStr, path::Path}; use toml::Value as TomlValue; use crate::error::*; @@ -495,8 +496,8 @@ impl Generator for DSC { source_file: Option<&Path>, dest_file: Option<&Path>, technique_metadata: bool, - ) -> Result<()> { - let mut files: HashMap = HashMap::new(); + ) -> Result> { + let mut files: Vec = Vec::new(); // TODO add global variable definitions for (rn, res) in gc.resources.iter() { for (sn, state) in res.states.iter() { @@ -510,9 +511,12 @@ impl Generator for DSC { self.reset_context(); // get header - let header = match files.get(&file_to_create) { - Some(s) => s.to_string(), - None => { + let header = match files + .iter() + .find(|f| f.destination == Some(file_to_create.clone().into())) + { + Some(s) if s.content.is_some() => s.content.as_ref().unwrap().to_string(), + _ => { if technique_metadata { self.generate_ncf_metadata(rn, res)? // TODO dsc } else { @@ -573,25 +577,14 @@ function {resource_name}-{state_name} {{ parameters = parameters, methods = methods ); - files.insert(file_to_create, content); + files.push(ActionResult::new( + Format::DSC, + Some(file_to_create.into()), + Some(content.to_string()), + )); } } - - // create file if needed - if files.is_empty() { - match dest_file { - Some(filename) => File::create(filename).expect("Could not create output file"), - None => return Err(Error::new("No file to create".to_owned())), - }; - } - - // write to file - for (name, content) in files.iter() { - let mut file = File::create(name).expect("Could not create output file"); - file.write_all(content.as_bytes()) - .expect("Could not write content into output file"); - } - Ok(()) + Ok(files) } } diff --git a/rudder-lang/src/generator/json.rs b/rudder-lang/src/generator/json.rs index de84e1c6d2c..01084f2eac4 100644 --- a/rudder-lang/src/generator/json.rs +++ b/rudder-lang/src/generator/json.rs @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: 2019-2020 Normation SAS use super::Generator; -use crate::{ir::ir2::IR2, error::*, technique::Technique}; +use crate::{error::*, ir::ir2::IR2, technique::Technique, ActionResult, Format}; use std::convert::From; -use std::{fs::File, io::Write, path::Path}; +use std::path::Path; pub struct JSON; @@ -16,12 +16,15 @@ impl Generator for JSON { _source_file: Option<&Path>, dest_file: Option<&Path>, _policy_metadata: bool, - ) -> Result<()> { + ) -> Result> { let content = Technique::from(gc).to_json()?; - File::create(dest_file.expect("No destination to write on")) - .expect("Could not create output file") - .write_all(content.as_bytes()) - .expect("Could not write content into output file"); - Ok(()) + Ok(vec![ActionResult::new( + Format::DSC, + match dest_file { + Some(path) => path.to_str().map(|refstr| refstr.into()), + None => None, + }, + Some(content.to_string()), + )]) } } diff --git a/rudder-lang/src/generator/markdown.rs b/rudder-lang/src/generator/markdown.rs index 2da834a7fea..1098294aca1 100644 --- a/rudder-lang/src/generator/markdown.rs +++ b/rudder-lang/src/generator/markdown.rs @@ -2,9 +2,9 @@ /// Generates markdown use super::Generator; -use crate::{ir::resource::StateDef, ir::ir2::IR2, error::*}; +use crate::{error::*, ir::ir2::IR2, ir::resource::StateDef, ActionResult, Format}; use std::cmp::Ordering; -use std::{collections::HashMap, fs::File, io::Write, path::Path}; +use std::path::{Path, PathBuf}; pub struct Markdown {} impl Markdown { @@ -21,15 +21,8 @@ impl Generator for Markdown { _source_file: Option<&Path>, _dest_file: Option<&Path>, _policy_metadata: bool, - ) -> Result<()> { - let files = Self::render(gc, None)?; - - for (name, content) in files.iter() { - let mut file = File::create(name).expect("Could not create output file"); - file.write_all(content.as_bytes()) - .expect("Could not write content into output file"); - } - Ok(()) + ) -> Result> { + Self::render(gc, None) } } @@ -96,8 +89,8 @@ impl Markdown { /// renders doc from an IR, with an optional resource filter /// When filter resource is None the whole IR is rendered - fn render(gc: &IR2, resource_filter: Option) -> Result> { - let mut files: HashMap = HashMap::new(); + fn render(gc: &IR2, resource_filter: Option) -> Result> { + let mut files: Vec = Vec::new(); for (resource_name, resource) in gc.resources.iter() { if let Some(ref name) = resource_filter { @@ -108,7 +101,8 @@ impl Markdown { // one file by resource // FIXME proper destination - let resource_file = format!("target/docs/std/{}.md", resource_name.fragment()); + let resource_file: PathBuf = + format!("target/docs/std/{}.md", resource_name.fragment()).into(); let mut out = vec![]; out.push(format!("# {}", resource_name.fragment())); @@ -209,7 +203,11 @@ impl Markdown { out.extend(Self::render_parameters(&state.metadata).iter().cloned()); } - files.insert(resource_file, out.join("\n\n")); + files.push(ActionResult::new( + Format::Markdown, + Some(resource_file.into()), + Some(out.join("\n\n")), + )); } Ok(files) diff --git a/rudder-lang/src/io.rs b/rudder-lang/src/io.rs index b738f07ddf1..2e7b36ac125 100644 --- a/rudder-lang/src/io.rs +++ b/rudder-lang/src/io.rs @@ -1,25 +1,19 @@ // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2019-2020 Normation SAS +use crate::{error::*, generator::Format, opt::Options, Action}; use serde::Deserialize; use std::{fmt, path::PathBuf, str::FromStr}; -use crate::{error::*, generator::Format, opt::GenericOptions, Action}; - -// deserialized config file sub struct #[derive(Clone, Debug, Deserialize)] -struct ActionConfig { - input: Option, - output: Option, - format: Option, - // action is defined later in the code, should never be deserialized: set to None at first - #[serde(skip, default)] - action: Option, +struct LibsConfig { + stdlib: PathBuf, } #[derive(Clone, Debug, Deserialize)] -struct LibsConfig { - stdlib: PathBuf, +struct IOPaths { + input: Option, + output: Option, } // deserialized config file main struct @@ -27,25 +21,27 @@ struct LibsConfig { struct Config { #[serde(rename = "shared")] libs: LibsConfig, - compile: ActionConfig, - translate: ActionConfig, + compile: IOPaths, + migrate: IOPaths, + technique_generate: IOPaths, + technique_read: IOPaths, } /// IO context deduced from arguments (structopt) and config file (Config) -// must always reflect GenericOptions + add unique fields -#[derive(Clone, Debug)] +// must always reflect Options + add unique fields +#[derive(Clone, Debug, PartialEq)] pub struct IOContext { // GenericOption reflection pub stdlib: PathBuf, - pub input: PathBuf, - pub output: PathBuf, + pub input: Option, + pub output: Option, // Unique fields pub format: Format, pub action: Action, } // TODO might try to merge io.rs in here impl IOContext { - pub fn set(action: Action, opt: &GenericOptions, format: Option) -> Result { + pub fn new(action: Action, opt: &Options, format: Option) -> Result { let config: Config = match std::fs::read_to_string(&opt.config_file) { Err(e) => { return Err(Error::new(format!( @@ -64,14 +60,27 @@ impl IOContext { }, }; - let action_config = get_opt_action(action, &config); - let (output, format) = get_output(&action_config, &opt.input, &opt.output, format)?; + let action_config = match action { + Action::Compile => config.compile, + Action::GenerateTechnique => config.technique_generate, + Action::Migrate => config.migrate, + Action::ReadTechnique => config.technique_read, + }; + let input = get_input(&action_config.input, &opt.input, opt.stdin)?; + let (output, format) = get_output( + &action_config.output, + action, + &input, + &opt.output, + opt.stdout, + format, + )?; Ok(IOContext { stdlib: config.libs.stdlib.clone(), - input: get_input(&action_config, &opt.input)?, + input, output, - action: action_config.action.unwrap(), + action, format, }) } @@ -89,111 +98,119 @@ impl fmt::Display for IOContext { } } -fn get_opt_action(action: Action, config: &Config) -> ActionConfig { - info!("Action requested: {}", action); - ActionConfig { - action: Some(action), - ..config.compile.clone() +/// If stdin, returns None. Else if input and config lead to existing file, Some(file). Else, error. +fn get_input( + config_path: &Option, + input: &Option, + is_stdin: bool, +) -> Result> { + if is_stdin { + if input.is_some() { + info!("Input file not used because of the --stdin option"); + } + return Ok(None); } -} - -/// get explicit input file or error -fn get_input(config: &ActionConfig, input: &Option) -> Result { - Ok(match &input { - Some(technique_path) => technique_path.to_path_buf(), - None => match &config.input { - Some(config_input) => { - if config_input.is_file() { - config_input.to_path_buf() - } else { - return Err(Error::new( - "Could not determine input file: no parameters and configured input is a directory".to_owned(), - )); - } - } - None => { + Ok(Some(match (input, config_path) { + (Some(i), _) if i.is_file() => i.to_owned(), + (Some(i), Some(c)) => { + let path = c.join(i); + if path.is_file() { + path.to_owned() + } else { return Err(Error::new( - "Could not determine input file: neither parameters nor configured input" - .to_owned(), - )) + "Commands: input does not match any existing file".to_owned(), + )); } - }, - }) + } + (None, Some(c)) if c.is_file() => c.to_owned(), + _ => { + return Err(Error::new( + "Commands: no input or input does not match any existing file".to_owned(), + )) + } + })) } -/// get explicit output file or error +/// get explicit output file fn get_output( - config: &ActionConfig, + config_path: &Option, + action: Action, input: &Option, - output: &Option, + argv_output: &Option, + is_stdout: bool, format: Option, -) -> Result<(PathBuf, Format)> { - let technique = match &output { - Some(technique_path) => technique_path.to_path_buf(), - None => match &config.output { - Some(config_output) => { - if config_output.is_file() { - config_output.to_path_buf() - } else { - match &input { - Some(input) => input.to_path_buf(), - None => return Err(Error::new( - "Could not determine output file: no parameters, configured output is a directory and no input to base destination path on".to_owned(), - )) - } - } - }, - None => match &input { - Some(input) => input.to_path_buf(), - None => return Err(Error::new( - "Could not determine output file: neither parameters nor configured input nor input to base destination path on".to_owned(), - )) +) -> Result<(Option, Format)> { + if is_stdout && argv_output.is_some() { + warn!("commands: stdout option conflicts with output option. Priority to the former."); + } + // is_stdout OR exception for Generate Technique which is designed to work from stdin: default stdout unless output specified + if is_stdout || (action == Action::GenerateTechnique && argv_output == &None) { + return Ok((None, get_output_format(action, format, &None)?.1)); + } + + let technique = Some(match (&argv_output, config_path, input) { + (Some(o), _, _) if o.parent().filter(|p| p.is_dir()).is_some() => o.to_owned(), + (Some(o), Some(c), _) => { + let path = c.join(o); + if path.parent().filter(|p| p.is_dir()).is_some() { + path + } else { + return Err(Error::new( + "Commands: paths do not match any existing directory".to_owned(), + )); } } - }; + (None, Some(c), _) if c.is_file() => c.to_owned(), + (_, _, Some(i)) => i.to_owned(), + (_, _, None) => { + return Err(Error::new(format!( + "Commands: no parameters or configuration output or input to base output file on" + ))) + } + }); // format is part of output file so it makes sense to return it from this function plus it needs to be defined here to update output if needed - let (format, format_as_str) = get_output_format(config, format, &technique)?; - if technique.ends_with(&format_as_str) {} - Ok((technique.with_extension(&format_as_str), format)) + let (format_as_str, format) = get_output_format(action, format, &technique)?; + Ok(( + technique.map(|output| output.with_extension(&format_as_str)), + format, + )) } /// get explicit output. If no explicit output get default path + filename. I none, use input path (and update format). If none worked, error fn get_output_format( - config: &ActionConfig, + action: Action, format: Option, - output: &PathBuf, -) -> Result<(Format, String)> { - if config.action == Some(Action::Compile) && format.is_some() { - info!("Command line format used"); - } else if config.action == Some(Action::Compile) && config.format.is_some() { - info!("Configuration format used"); + output: &Option, +) -> Result<(String, Format)> { + if action == Action::Compile && format.is_some() { + info!("Command line format option used"); } - let fmt: Format = match format.as_ref().or_else(|| config.format.as_ref()) { - Some(fmt) => fmt.clone(), - None => { - info!("Output technique format used"); - match output.extension().and_then(|fmt| fmt.to_str()) { - Some(fmt) => Format::from_str(fmt)?, + // All formats but Compile are hardcoded in Opt implementation, so this is partly double check + match (action, format) { + (Action::Compile, Some(fmt)) if fmt == Format::CFEngine || fmt == Format::DSC => { + Ok((format!("{}.{}", "rl", fmt), fmt)) + } + (Action::Compile, _) => { + info!("Commands: missing or invalid format, deducing it from output file extension"); + let ext = match output { + Some(o) => o.extension(), + None => None, + }; + match ext.and_then(|fmt| fmt.to_str()) { + Some(fmt) => { + let fmt = Format::from_str(fmt)?; + return Ok((format!("{}", fmt), fmt)) + } None => return Err(Error::new( - "Could not output file format: neither from argument nor configuration file, and output technique has no defined format".to_owned(), + "Commands: missing or invalid format, plus unrecognized or invalid output file extension".to_owned(), )) } } - }; - - match config.action { - Some(Action::Compile) if fmt == Format::CFEngine || fmt == Format::DSC => { - Ok((fmt.clone(), format!("{}.{}", "rl", fmt))) + (_, Some(fmt)) => Ok((format!("{}", fmt), fmt)), + (_, None) => { + panic!("Commands: format should have been defined earlier in program execution") } - Some(Action::GenerateTechnique) => Ok((Format::JSON, "json".to_owned())), - Some(Action::Migrate) => Ok((Format::RudderLang, "rl".to_owned())), - Some(Action::ReadTechnique) => Ok((Format::JSON, "json".to_owned())), - _ => Err(Error::new(format!( - "Could not determine format: {} is not a valid format for {}", - fmt, - config.action.unwrap() - ))), } } diff --git a/rudder-lang/src/ir/value.rs b/rudder-lang/src/ir/value.rs index a0d50de9865..cc9d8a35bed 100644 --- a/rudder-lang/src/ir/value.rs +++ b/rudder-lang/src/ir/value.rs @@ -2,7 +2,8 @@ // SPDX-FileCopyrightText: 2019-2020 Normation SAS use super::{ - context::VarContext, context::Type, + context::Type, + context::VarContext, enums::{EnumExpression, EnumList}, }; use crate::{error::*, parser::*}; @@ -158,8 +159,7 @@ impl<'src> Value<'src> { } } - pub fn context_check(&self, context: &VarContext<'src>) -> Result<()> - { + pub fn context_check(&self, context: &VarContext<'src>) -> Result<()> { match self { Value::String(s) => { map_results(s.data.iter(), |e| match e { @@ -194,25 +194,28 @@ impl<'src> ComplexValue<'src> { context: &VarContext<'src>, pvalue: PComplexValue<'src>, ) -> Result { - let cases= map_vec_results(pvalue.cases.into_iter(), - |(case,value)| { - let case = enum_list.canonify_expression(context, case)?; - let value = match value { - None => None, - Some(v) => Some(Value::from_pvalue(enum_list, context, v)?), - }; - Ok((case,value)) - } - )?; - Ok(ComplexValue{ source: pvalue.source, cases }) + let cases = map_vec_results(pvalue.cases.into_iter(), |(case, value)| { + let case = enum_list.canonify_expression(context, case)?; + let value = match value { + None => None, + Some(v) => Some(Value::from_pvalue(enum_list, context, v)?), + }; + Ok((case, value)) + })?; + Ok(ComplexValue { + source: pvalue.source, + cases, + }) } - pub fn extend(&mut self, - enum_list: &EnumList<'src>, - context: &VarContext<'src>, - pvalue: PComplexValue<'src>, + pub fn extend( + &mut self, + enum_list: &EnumList<'src>, + context: &VarContext<'src>, + pvalue: PComplexValue<'src>, ) -> Result<()> { - let ComplexValue{ source, mut cases } = ComplexValue::from_pcomplex_value(enum_list, context, pvalue)?; + let ComplexValue { source, mut cases } = + ComplexValue::from_pcomplex_value(enum_list, context, pvalue)?; self.cases.append(&mut cases); Ok(()) } @@ -225,29 +228,34 @@ impl<'src> ComplexValue<'src> { .ok_or_else(|| err!(self.source, "Case list doesn't have any value")) } - pub fn context_check(&self, context: &VarContext<'src>) -> Result<()> - { - map_results(self.cases.iter(), - |x| - match &x.1 { - Some(v) => v.context_check(context), - None => Ok(()) - } - ) + pub fn context_check(&self, context: &VarContext<'src>) -> Result<()> { + map_results(self.cases.iter(), |x| match &x.1 { + Some(v) => v.context_check(context), + None => Ok(()), + }) } - pub fn verify(&self, enum_list: &EnumList<'src>,) -> Result<()> { + pub fn verify(&self, enum_list: &EnumList<'src>) -> Result<()> { // check enums let mut errors = enum_list.evaluate(&self.cases, "TODO".into()); // check type - let init_val = self.first_value().expect("Verify a complex value only after knowing it has values!"); // type should have already been extracted once + let init_val = self + .first_value() + .expect("Verify a complex value only after knowing it has values!"); // type should have already been extracted once let init_type = Type::from_value(init_val); for value in self.cases.iter() { if let Some(val) = &value.1 { let type_ = Type::from_value(val); if type_ != init_type { // TODO implement Display for Value - errors.push(err!(self.source, "{:?}:{} is not the same type as {:?}:{}", val, type_, init_val, init_type)); + errors.push(err!( + self.source, + "{:?}:{} is not the same type as {:?}:{}", + val, + type_, + init_val, + init_type + )); } } } diff --git a/rudder-lang/src/lib.rs b/rudder-lang/src/lib.rs index 01e645d028c..0444f15a891 100644 --- a/rudder-lang/src/lib.rs +++ b/rudder-lang/src/lib.rs @@ -16,7 +16,8 @@ mod ir; pub mod opt; pub use generator::Format; pub mod cfstrings; -pub mod logger; +pub mod migrate; +pub mod output; mod parser; pub mod rudderlang_lib; pub mod technique; @@ -24,7 +25,7 @@ pub mod technique; use crate::technique::TechniqueFmt; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, path::PathBuf}; #[derive(Clone, Copy, PartialEq, Deserialize)] pub enum Action { @@ -62,27 +63,24 @@ impl fmt::Debug for Action { } } -#[derive(Serialize)] +#[derive(Serialize, Clone)] pub struct ActionResult { pub format: Format, - pub destination: Option, // None means content has directly been written in the following data field - pub data: Option, // None means content has directly been written in a file + pub destination: Option, // None means it will be printed to stdout + pub content: Option, // None means there has been an error } impl ActionResult { /// ActionResult s can only hold either a destination file or directly the content /// If both respective fields hold a variable (or both do not), means there is a bug pub fn new( format: Format, + destination: Option, technique: Option, - destination: Option, ) -> Self { - if destination.is_none() == technique.is_none() { - panic!("A rudderc command should always result either in a file output or a json wrapped content. Not both. Not none") - } Self { format, - data: technique, destination, + content: technique, } } @@ -90,8 +88,8 @@ impl ActionResult { pub fn default() -> Self { Self { format: Format::CFEngine, - data: None, destination: None, + content: None, } } } diff --git a/rudder-lang/src/migrate.rs b/rudder-lang/src/migrate.rs new file mode 100644 index 00000000000..80f549205e8 --- /dev/null +++ b/rudder-lang/src/migrate.rs @@ -0,0 +1,8 @@ +use crate::{error::Result, io::IOContext, ActionResult}; + +// TODO Migrate: call cf_to_json perl script then call json->rl == Technique generate() +/// Generates RudderLang from CFEngine +pub fn migrate(ctx: &IOContext) -> Result> { + // Ok(vec![ActionResult::default()]) + unimplemented!() +} diff --git a/rudder-lang/src/opt.rs b/rudder-lang/src/opt.rs index 8e193f78457..8d58e3b2fbc 100644 --- a/rudder-lang/src/opt.rs +++ b/rudder-lang/src/opt.rs @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2019-2020 Normation SAS -use crate::{error::Result, generator::Format, io::IOContext, logger::Output, Action}; +use crate::{error::Result, generator::Format, io::IOContext, output::Logs, Action}; use log::LevelFilter; -use std::path::PathBuf; +use serde::Deserialize; +use std::{cmp::PartialEq, path::PathBuf}; use structopt::StructOpt; -#[derive(Debug, StructOpt)] +#[derive(Debug, StructOpt, Deserialize, PartialEq)] #[structopt(rename_all = "kebab-case")] /// Rudderc abilities are callable through subcommands, namely , , , , /// allowing it to perform generation or translation from / into the following formats : [JSON, RudderLang, CFengine, DSC]. @@ -20,136 +21,199 @@ pub enum Opt { /// Generates a RudderLang technique from a CFEngine technique Migrate { #[structopt(flatten)] - options: GenericOptions, + options: Options, /// Use json logs instead of human readable output + /// + /// This option will print a single JSON object that will contain logs, errors and generated data (or the file where it has been generated) + /// + /// JSON output format is always the same, whichever action is chosen. + /// However, some fields (data and destination file) could be set to `null`, make sure to handle `null`s properly. + /// + /// Note that NO_COLOR specs apply by default for json output. + /// + /// Also note that setting NO_COLOR manually in your env will also work #[structopt(long, short)] - json_log: bool, + json_logs: bool, }, /// Generates either a DSC / CFEngine technique (`--format` option) from a RudderLang technique Compile { #[structopt(flatten)] - options: GenericOptions, + options: Options, /// Use json logs instead of human readable output + /// + /// This option will print a single JSON object that will contain logs, errors and generated data (or the file where it has been generated) + /// + /// JSON output format is always the same, whichever action is chosen. + /// However, some fields (data and destination file) could be set to `null`, make sure to handle `null`s properly. + /// + /// Note that NO_COLOR specs apply by default for json output. + /// + /// Also note that setting NO_COLOR manually in your env will also work #[structopt(long, short)] - json_log: bool, + json_logs: bool, /// Enforce a compiler output format (overrides configuration format) - /// Handled formats: [ "cf" (alias "cfengine"), "dsc" ] #[structopt(long, short, possible_values = &["cf", "cfengine", "dsc"])] format: Option, }, } + +/// A technique can either be used with one of the two following subcommands: `read` (from rudderlang to json) or `generate` (from json to cfengine or dsc or rudderlang) +#[derive(Debug, StructOpt, Deserialize, PartialEq)] +#[structopt(rename_all = "kebab-case")] +pub enum Technique { + /// Generates a JSON technique from a RudderLang technique + Read { + #[structopt(flatten)] + options: Options, + }, + /// Generates a JSON object that comes with RudderLang + DSC + CFEngine technique from a JSON technique + Generate { + #[structopt(flatten)] + options: Options, + }, +} + +#[derive(Clone, Debug, StructOpt, Deserialize, PartialEq)] +#[structopt(rename_all = "kebab-case")] +pub struct Options { + /// Path of the configuration file to use. + /// A configuration file is required (containing at least stdlib and generic_methods paths) + #[structopt(long, short, default_value = "/opt/rudder/etc/rudderc.conf")] + pub config_file: PathBuf, + + /// Input file path. + /// + /// If option path does not exist, concat config input with option. + #[structopt(long, short)] + pub input: Option, + + /// Output file path. + /// + /// If option path does not exist, concat config output with option. + /// + ///Else base output on input. + #[structopt(long, short)] + pub output: Option, + + /// rudderc output logs verbosity. + #[structopt( + long, + short, + possible_values = &["off", "trace", "debug", "info", "warn", "error"], + default_value = "warn" + )] + pub log_level: LevelFilter, + + /// Takes stdin as an input rather than using a file. Overwrites input file option + /// + /// Please note that the directory must exist in order to create the output. + #[structopt(long)] + pub stdin: bool, + + /// Takes stdout as an output rather than using a file. Overwrites output file option. Dismiss logs including errors + #[structopt(long)] + pub stdout: bool, + + /// Generates a backtrace in case an error occurs + #[structopt(long, short)] + pub backtrace: bool, +} + impl Opt { - pub fn extract_logging_infos(&self) -> (Output, LevelFilter, bool) { - match self { + pub fn extract_logging_infos(&self) -> (Logs, LevelFilter, bool) { + let (output, level, backtrace_enabled) = match self { Self::Compile { - options, json_log, .. + options, json_logs, .. } => { - let logger = match json_log { - true => Output::JSON, - false => Output::Terminal, + let output = match (json_logs, options.stdout) { + (_, true) => Logs::None, + (true, false) => Logs::JSON, + (false, false) => Logs::Terminal, }; - (logger, options.log_level, options.backtrace) + (output, options.log_level, options.backtrace) } - Self::Migrate { options, json_log } => { - let logger = match json_log { - true => Output::JSON, - false => Output::Terminal, + Self::Migrate { options, json_logs } => { + let output = match (json_logs, options.stdout) { + (_, true) => Logs::None, + (true, false) => Logs::JSON, + (false, false) => Logs::Terminal, }; - (logger, options.log_level, options.backtrace) + (output, options.log_level, options.backtrace) } Self::Technique(t) => t.extract_logging_infos(), + }; + // remove log colors if JSON format to make logs vec readable + if output == Logs::JSON { + std::env::set_var("NO_COLOR", "1"); } + (output, level, backtrace_enabled) } pub fn extract_parameters(&self) -> Result { match self { Self::Compile { options, format, .. - } => IOContext::set(self.to_action(), options, format.clone()), - Self::Migrate { options, .. } => IOContext::set(self.to_action(), options, None), + } => IOContext::new(self.as_action(), options, format.clone()), + Self::Migrate { options, .. } => { + IOContext::new(self.as_action(), options, Some(Format::RudderLang)) + } Self::Technique(t) => t.extract_parameters(), } } - pub fn to_action(&self) -> Action { + pub fn as_action(&self) -> Action { match self { - Self::Technique(technique) => technique.to_action(), + Self::Technique(technique) => technique.as_action(), Self::Migrate { .. } => Action::Migrate, Self::Compile { .. } => Action::Compile, } } } -/// A technique can either be used with one of the two following subcommands: `read` (from rudderlang to json) or `generated` (from json to cfengine or dsc or rudderlang) -#[derive(Debug, StructOpt)] -#[structopt(rename_all = "kebab-case")] -pub enum Technique { - /// Generates a JSON technique from a RudderLang technique - Read { - #[structopt(flatten)] - options: GenericOptions, - }, - /// Generates either a RudderLang / DSC / CFEngine technique (`--format` option) from a JSON technique - Generate { - #[structopt(flatten)] - options: GenericOptions, - }, -} impl Technique { - fn extract_logging_infos(&self) -> (Output, LevelFilter, bool) { - // might be Output::JSON instead + fn extract_logging_infos(&self) -> (Logs, LevelFilter, bool) { match self { - Self::Read { options } => (Output::JSON, options.log_level, options.backtrace), - Self::Generate { options, .. } => (Output::JSON, options.log_level, options.backtrace), + Self::Read { options } => { + let output = match options.stdout { + true => Logs::None, + false => Logs::JSON, + }; + (output, options.log_level, options.backtrace) + } + Self::Generate { options, .. } => { + let output = match options.stdout { + true => Logs::None, + false => Logs::JSON, + }; + (output, options.log_level, options.backtrace) + } } } fn extract_parameters(&self) -> Result { match self { - Self::Read { options } => IOContext::set(self.to_action(), options, None), - Self::Generate { options } => IOContext::set(self.to_action(), options, None), + Self::Read { options } => IOContext::new(self.as_action(), options, Some(Format::JSON)), + Self::Generate { options } => { + // exception: stdin + stdout are set by default + let mut options = options.clone(); + if options.input.is_none() { + options.stdin = true; + } + if options.output.is_none() && options.stdin { + options.stdout = true; + } + IOContext::new(self.as_action(), &options, Some(Format::JSON)) + } } } - fn to_action(&self) -> Action { + fn as_action(&self) -> Action { match self { Technique::Read { .. } => Action::ReadTechnique, Technique::Generate { .. } => Action::GenerateTechnique, } } } - -#[derive(Debug, StructOpt)] -#[structopt(rename_all = "kebab-case")] -pub struct GenericOptions { - /// Path of the configuration file to use. - /// A configuration file is required (containing at least stdlib and generic_methods paths) - #[structopt(long, short, default_value = "/opt/rudder/etc/rudderc.conf")] - pub config_file: PathBuf, - - /// Input file path. Overwrites base input - #[structopt(long, short)] - pub input: Option, - - /// Output file path, overwrites config and base output. - /// If neither an output nor a configuration file default output path is set, base output on input - #[structopt(long, short)] - pub output: Option, - - /// rudderc output logs verbosity. - #[structopt( - long, - short, - possible_values = &["off", "trace", "debug", "info", "warn", "error"], - default_value = "warn" - )] - pub log_level: LevelFilter, - - /// Generate a backtrace in case an error occurs - #[structopt(long, short)] - pub backtrace: bool, - // TODO add stdin + stdout -} diff --git a/rudder-lang/src/logger.rs b/rudder-lang/src/output.rs similarity index 72% rename from rudder-lang/src/logger.rs rename to rudder-lang/src/output.rs index c782f534b9c..90250d6621c 100644 --- a/rudder-lang/src/logger.rs +++ b/rudder-lang/src/output.rs @@ -9,7 +9,10 @@ use regex::Regex; use serde::Serialize; use std::fmt::Display; use std::{ - fmt, panic, + fmt, + fs::File, + io::Write, + panic, time::{SystemTime, UNIX_EPOCH}, }; @@ -38,8 +41,8 @@ impl Backtrace { (Some(start), None) => start.as_str().to_owned().into(), }) .and_then(|fmt_name| { - // do not put logger in the backtrace since it always ultimately calls panic_hook and print_backtrace - if fmt_name.starts_with("rudderc::logger") + // do not put output related calls in the backtrace since it always ultimately calls panic_hook and print_backtrace + if fmt_name.starts_with("rudderc::output") || fmt_name.starts_with("rudderc::error::Error::new") { return None; @@ -96,63 +99,70 @@ impl Display for Backtrace { } } -// NOTE that OutputFmtPanic is hardcoded, not serializable structure to pass to it -// struct OutputFmtPanic { +// NOTE that LogsFmtPanic is hardcoded, not serializable structure to pass to it +// struct LogsFmtPanic { // status: String, // message: String, // } #[derive(Serialize)] -struct OutputFmtOk { +struct LogsFmtOk { action: String, time: String, status: String, // either "success" or "failure" - source: String, // source file path + source: String, // source file path or STDIN logs: Logger, data: Vec, errors: Vec, } // This is subject to change. Would be better to find an existing log to variable solution +// TODO impl proper ram-log #[derive(Serialize)] pub struct Logger(Vec<(LevelFilter, String)>); impl Logger { pub fn init() -> Self { Self(Vec::new()) } - pub fn info(&mut self, msg: String) { - self.0.push((LevelFilter::Info, msg)) - } - pub fn error(&mut self, msg: String) { - self.0.push((LevelFilter::Error, msg)) - } - pub fn warn(&mut self, msg: String) { - self.0.push((LevelFilter::Warn, msg)) - } - pub fn debug(&mut self, msg: String) { - self.0.push((LevelFilter::Debug, msg)) - } - pub fn trace(&mut self, msg: String) { - self.0.push((LevelFilter::Trace, msg)) - } + // pub fn info(&mut self, msg: String) { + // self.0.push((LevelFilter::Info, msg)) + // } + // pub fn error(&mut self, msg: String) { + // self.0.push((LevelFilter::Error, msg)) + // } + // pub fn warn(&mut self, msg: String) { + // self.0.push((LevelFilter::Warn, msg)) + // } + // pub fn debug(&mut self, msg: String) { + // self.0.push((LevelFilter::Debug, msg)) + // } + // pub fn trace(&mut self, msg: String) { + // self.0.push((LevelFilter::Trace, msg)) + // } } -#[derive(Clone, Copy, PartialEq)] -pub enum Output { +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Logs { Terminal, JSON, + None, } -impl Output { - /// Initialize the logger +impl Logs { + /// Initialize the output generator pub fn init(self, log_level: LevelFilter, action: Action, backtrace_enabled: bool) { // Content called when panic! is encountered to close logger brackets and print error Self::set_panic_hook(self, action, backtrace_enabled); + if self == Logs::None { + // do not print any logs if output is aalready stdout + return; + } if backtrace_enabled { std::env::set_var("RUDDERC_BACKTRACE", "1"); } let mut log_builder = env_logger::Builder::new(); - // if self == Output::JSON { + // TODO integrate into log-ram + // if self == Logs::JSON { // // prevents any output stylization from the colored crate // colored::control::set_override(false); // // Note: record .file() and line() allow to get the origin of the print @@ -203,6 +213,7 @@ impl Output { /// panic default format takeover to print either proper json format output /// or rudder-lang own error logging format + // TODO integrate into log-ram fn set_panic_hook(self, action: Action, backtrace_enabled: bool) { panic::set_hook(Box::new(move |panic_info| { let e_message = match panic_info.payload().downcast_ref::<&str>() { @@ -225,7 +236,7 @@ impl Output { backtrace.map_or("".to_owned(), |bt| bt.to_string()) ); match self { - Output::JSON => println!( + Logs::JSON => println!( r#"{{ "result": {{ "status": "rudderc {}: unrecoverable error", @@ -236,19 +247,20 @@ impl Output { }},"#, action, message ), - Output::Terminal => error!("{}", message), + Logs::Terminal => error!("{}", message), + Logs::None => (), }; })); } - pub fn print(self, action: Action, source: T, result: Result>) { - let (is_success, data, errors) = match result { + pub fn print(self, action: Action, source: String, result: Result>) { + let (is_success, mut data, errors) = match result { Ok(data) => (true, data, Vec::new()), Err(e) => (false, Vec::new(), e.clean_format_list()), }; - let dest_files = data + let dest_files = &data .iter() - .filter_map(|res| res.destination.as_ref().map(|d| format!("'{}'", d))) + .filter_map(|res| res.destination.as_ref().map(|d| format!("'{:?}'", d))) .collect::>() .join(", "); @@ -258,16 +270,18 @@ impl Output { Err(_) => "could not get correct time".to_owned(), }; + // remove data if it has been written to a file to avoid duplicate content + self.print_to_file(&mut data, action); match self { - Output::JSON => { + Logs::JSON => { let status = if is_success { "success" } else { "failure" }; - let output = OutputFmtOk { + let output = LogsFmtOk { action: format!("{}", action), time, status: status.to_owned(), - source: format!("{}", source), - logs: Logger::init(), // TODO, put logs in ram rather than print to stdout - data, + source, + logs: Logger::init(), // faked ; TODO, put logs in ram rather than print to stdout + data: data.clone(), errors, }; let fmtoutput = serde_json::to_string_pretty(&output) @@ -275,7 +289,7 @@ impl Output { .unwrap(); // dev error if this does not work println!("{}", fmtoutput); } - Output::Terminal => { + Logs::Terminal => { if is_success { println!("{} written", dest_files); } else { @@ -285,6 +299,38 @@ impl Output { ); } } + Logs::None => (), + } + } + + // print content into a file and if successfully written, remove it from payload + fn print_to_file(&self, files: &mut Vec, action: Action) { + if self == &Logs::None { + if action == Action::GenerateTechnique { + println!( + "{}", + serde_json::to_string_pretty(&files) + .map_err(|e| format!("Building JSON output led to an error: {}", e)) + .unwrap() + ) // dev error if this does not work); + } else if files[0].content.is_some() { + // no error, expected length = 1 + println!("{}", &files[0].clone().content.unwrap()); + } else { + panic!("BUG! Output should be stdout but there is no content to print"); + } + } + // else + for file in files.iter_mut() { + if let (Some(dest), Some(content)) = (&file.destination, &file.content) { + let mut file_to_create = File::create(dest).expect("Could not create output file"); + file_to_create + .write_all(content.as_bytes()) + .expect("Could not write content into output file"); + file.content = None; + } else { + debug!("File content could not be printed"); + } } } } diff --git a/rudder-lang/src/rudderlang_lib.rs b/rudder-lang/src/rudderlang_lib.rs index 1bb842437ea..0fa1cda57f3 100644 --- a/rudder-lang/src/rudderlang_lib.rs +++ b/rudder-lang/src/rudderlang_lib.rs @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: 2019-2020 Normation SAS use crate::{ - ir::{resource::ResourceDef, resource::StateDef, ir1::IR1}, compile::parse_file, error::*, + ir::{ir1::IR1, resource::ResourceDef, resource::StateDef}, parser::{Token, PAST}, }; use colored::Colorize; @@ -58,8 +58,8 @@ impl<'src> RudderlangLib<'src> { for entry in walker { match entry { Ok(entry) => { - let path = entry.path(); - parse_file(&mut past, sources, path)?; + let path = entry.into_path(); + parse_file(&mut past, sources, &Some(path))?; } Err(err) => { return Err(err!( diff --git a/rudder-lang/src/technique.rs b/rudder-lang/src/technique.rs index 1b40f0b3426..b8a80874d4c 100644 --- a/rudder-lang/src/technique.rs +++ b/rudder-lang/src/technique.rs @@ -4,8 +4,8 @@ mod from_ir; mod generate; mod read; -pub use generate::generate_technique; -pub use read::read_technique; +pub use generate::technique_generate; +pub use read::technique_read; use crate::{ cfstrings, @@ -17,7 +17,7 @@ use colored::Colorize; use lazy_static::lazy_static; use regex::{Captures, Regex}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{fs, str}; +use std::{fs, io::Read, str}; use typed_arena::Arena; // might change later @@ -30,10 +30,10 @@ pub fn generate(context: &IOContext) -> Result<()> { let sources: Arena = Arena::new(); let lib = RudderlangLib::new(&context.stdlib, &sources)?; let rudderlang_technique = Technique::from_json(&context)?.to_rudderlang(&lib)?; - let output_path = context.output.to_string_lossy(); // will disapear soon, return string directly - fs::write(&context.output, rudderlang_technique).map_err(|e| err_wrapper(&output_path, e))?; + // let output_path = context.output.unwrap().to_string_lossy(); + // fs::write(&context.output, rudderlang_technique).map_err(|e| err_wrapper(&output_path, e))?; Ok(()) } @@ -66,24 +66,29 @@ pub struct Technique { impl Technique { /// creates a Technique that will be used to generate a string representation of a rudderlang or json technique fn from_json(context: &IOContext) -> Result { - let input_path = &context.input.to_string_lossy(); + let (input, content) = match &context.input { + Some(file_path) => { + let input = file_path.to_string_lossy().to_string(); + let content = fs::read_to_string(file_path).map_err(|e| err_wrapper(&input, e))?; + (input, content) + } + None => { + let mut buffer = String::new(); + std::io::stdin() + .read_to_string(&mut buffer) + .map_err(|e| err_wrapper("STDIN", e))?; + ("STDIN".to_owned(), buffer) + } + }; info!( "{} from {}", "Processing generation".bright_green(), - input_path.bright_yellow(), + input.bright_yellow(), ); + info!("|- {} {}", "Parsing".bright_green(), input.bright_yellow()); - info!( - "|- {} {}", - "Parsing".bright_green(), - input_path.bright_yellow() - ); - - let json_str = - &fs::read_to_string(&context.input).map_err(|e| err_wrapper(input_path, e))?; - - serde_json::from_str::(json_str) + serde_json::from_str::(&content) .map_err(|e| Error::new(format!("Technique from JSON: {}", e))) } diff --git a/rudder-lang/src/technique/generate.rs b/rudder-lang/src/technique/generate.rs new file mode 100644 index 00000000000..39881e964fc --- /dev/null +++ b/rudder-lang/src/technique/generate.rs @@ -0,0 +1,6 @@ +use crate::{error::Result, io::IOContext, ActionResult}; + +pub fn technique_generate(ctx: &IOContext) -> Result> { + // Ok(vec![ActionResult::default()]) + unimplemented!() +} diff --git a/rudder-lang/src/technique/read.rs b/rudder-lang/src/technique/read.rs new file mode 100644 index 00000000000..a5a3d1991d0 --- /dev/null +++ b/rudder-lang/src/technique/read.rs @@ -0,0 +1,19 @@ +use crate::{ + compile::technique_to_ir, error::Result, generator::Format, io::IOContext, + technique::Technique, ActionResult, +}; +use typed_arena::Arena; + +/// takes a RudderLang technique, and generate a JSON technique that wraps it up +// TODO report errors directly into the output (+data) +pub fn technique_read(ctx: &IOContext) -> Result> { + let sources = Arena::new(); + let technique = Technique::from(&technique_to_ir(ctx, &sources)?).to_json()?; + + // TODO Option -> output -> Some else None + Ok(vec![ActionResult::new( + Format::JSON, + ctx.output.clone(), + Some(technique), + )]) +} diff --git a/rudder-lang/tests/techniques/6.1.rc5/technique.rl b/rudder-lang/tests/techniques/6.1.rc5/technique.rl index 583aa8a13b7..97ab5a002c5 100644 --- a/rudder-lang/tests/techniques/6.1.rc5/technique.rl +++ b/rudder-lang/tests/techniques/6.1.rc5/technique.rl @@ -13,5 +13,5 @@ resource normal(parameter_wdd,paramtest) normal state technique() { @component = "Condition once" - condition("mycond").once() dfffas condition_once_mycond + condition("mycond").once() as condition_once_mycond } diff --git a/rudder-lang/tests/techniques/6.1.rc5/technique.rl.cf b/rudder-lang/tests/techniques/6.1.rc5/technique.rl.cf new file mode 100644 index 00000000000..3b8ed07dd80 --- /dev/null +++ b/rudder-lang/tests/techniques/6.1.rc5/technique.rl.cf @@ -0,0 +1,22 @@ +# generated by rudderc +# @name normal +# @version unknown + +bundle agent normal_technique(parameter_wdd, paramtest) { + + vars: + "resources_dir" string => "${this.promise_dirname}/resources"; + "args" slist => {"parameter_wdd", "paramtest"}; + "report_param" string => join("_", args); + "full_class_prefix" string => canonify("normal_technique_${report_param}"); + "class_prefix" string => string_head("${full_class_prefix}", "1000"); + + methods: + # Condition once: + # + # condition("mycond").once() as condition_once_mycond + # + "${report_data.directive_id}_0" usebundle => _method_reporting_context("Condition once", "mycond"); + "${report_data.directive_id}_0" usebundle => condition_once("mycond"); + +} \ No newline at end of file diff --git a/rudder-lang/tests/techniques/6.1.rc5/technique.rl.json b/rudder-lang/tests/techniques/6.1.rc5/technique.rl.json new file mode 100644 index 00000000000..8b2f7d7d28f --- /dev/null +++ b/rudder-lang/tests/techniques/6.1.rc5/technique.rl.json @@ -0,0 +1,38 @@ +{ + "type": "ncf_technique", + "version": 2, + "data": { + "bundle_name": "normal", + "description": "ewf", + "name": "normal", + "version": 0, + "parameter": [ + { + "id": "c6e6cc3a-9ce8-4889-bccc-6bfc1b091d0d", + "name": "parameter wdd", + "description": "" + }, + { + "id": "d74a03dd-5b0b-4b06-8dcf-b4e0cb387c60", + "name": "paramtest", + "description": "" + } + ], + "category": "", + "method_calls": [ + { + "parameters": [ + { + "name": "condition_prefix", + "value": "mycond", + "$errors": [] + } + ], + "class_context": "any", + "method_name": "condition_once", + "component": "Condition once" + } + ], + "resources": [] + } +} \ No newline at end of file diff --git a/rudder-lang/tests/techniques/simplest/technique.json b/rudder-lang/tests/techniques/simplest/technique.json index 21a0b412bf9..7d4d4aa09e6 100644 --- a/rudder-lang/tests/techniques/simplest/technique.json +++ b/rudder-lang/tests/techniques/simplest/technique.json @@ -1,26 +1,27 @@ { - "type": "ncf_technique", - "version": 1, - "data": { - "name": "simplest", - "description": "rudderlang simplest for a complete loop", - "version": "1.0", - "bundle_name": "simplest", - "bundle_args": [], - "parameter": [], - "method_calls": [ - { - "class_context": "debian", - "component": "File absent", - "method_name": "file_absent", - "parameters": [ - { - "name": "filename", - "value": "tmp", - "$errors": [] - } - ] - } - ] - } -} + "type": "ncf_technique", + "version": 2, + "data": { + "bundle_name": "simplest", + "description": "rudderlang simplest for a complete loop", + "name": "simplest", + "version": 0, + "parameter": [], + "category": "ncf_technique", + "method_calls": [ + { + "parameters": [ + { + "name": "path", + "value": "tmp", + "$errors": [] + } + ], + "class_context": "any.(debian)", + "method_name": "file_absent", + "component": "File absent" + } + ], + "resources": [] + } +} \ No newline at end of file diff --git a/rudder-lang/tests/tmp/s_state_case/technique.rl b/rudder-lang/tests/tmp/s_state_case/technique.rl deleted file mode 100644 index 0fa150cdfd0..00000000000 --- a/rudder-lang/tests/tmp/s_state_case/technique.rl +++ /dev/null @@ -1,41 +0,0 @@ -# generated by rudderc -# @name unknown -# @version unknown - -bundle agent Configure_NTP_technique { - - vars: - "resources_dir" string => "${this.promise_dirname}/resources"; - "args" slist => {}; - "report_param" string => join("_", args); - "full_class_prefix" string => canonify("Configure_NTP_technique_${report_param}"); - "class_prefix" string => string_head("${full_class_prefix}", "1000"); - - methods: - # any: - # - # file("/tmp").absent() - # - "${report_data.directive_id}_0" usebundle => _method_reporting_context("any", "/tmp"), - if => concat("ubuntu"); - "${report_data.directive_id}_0" usebundle => file_absent("/tmp"), - if => concat("ubuntu"); - "${report_data.directive_id}_0" usebundle => _classes_noop(canonify("${class_prefix}_file_absent_/tmp")), - unless => concat("ubuntu"); - "${report_data.directive_id}_0" usebundle => log_rudder("Skipping method 'any' with key parameter '/tmp' since condition 'ubuntu' is not reached", "/tmp", canonify("${class_prefix}_file_absent_/tmp"), canonify("${class_prefix}_file_absent_/tmp"), @{args}), - unless => concat("ubuntu"); - # any: - # - # file("/tmp").present() - # - "${report_data.directive_id}_0" usebundle => _method_reporting_context("any", "/tmp"), - if => concat("debian"); - "${report_data.directive_id}_0" usebundle => file_present("/tmp"), - if => concat("debian"); - "${report_data.directive_id}_0" usebundle => _classes_noop(canonify("${class_prefix}_file_present_/tmp")), - unless => concat("debian"); - "${report_data.directive_id}_0" usebundle => log_rudder("Skipping method 'any' with key parameter '/tmp' since condition 'debian' is not reached", "/tmp", canonify("${class_prefix}_file_present_/tmp"), canonify("${class_prefix}_file_present_/tmp"), @{args}), - unless => concat("debian"); - "${report_data.directive_id}_0" usebundle => log_rudder_mode("log_info", "ok", "None", "log_info"); - -} \ No newline at end of file diff --git a/rudder-lang/tests/utils/mod.rs b/rudder-lang/tests/utils/mod.rs index 9d6f91da044..ec70020a3dd 100644 --- a/rudder-lang/tests/utils/mod.rs +++ b/rudder-lang/tests/utils/mod.rs @@ -38,7 +38,7 @@ pub fn test_real_file(technique_name: &str, format: &Format) { /// Core test function that actually compares the file compilation result to expected result fn test_file(source: &Path, dest: &Path, technique_name: &str, format: &Format) { - let result = compile_file(&source, &dest, technique_name, format); + let result = compile(&source, &dest, technique_name, format); assert_eq!( result.is_ok(), should_compile(technique_name), @@ -62,7 +62,7 @@ fn should_compile(technique_name: &str) -> bool { } /// Compile technique from base crate and expose its result -fn compile_file( +fn compile( source: &Path, dest: &Path, technique_name: &str, @@ -70,12 +70,12 @@ fn compile_file( ) -> Result<(), String> { let io = rudderc::io::IOContext { stdlib: PathBuf::from("libs/"), - source: source.to_path_buf(), - dest: dest.to_path_buf(), + input: Some(source.to_path_buf()), + output: Some(dest.to_path_buf()), action: Action::Compile, format: format.clone(), }; - match rudderc::compile::compile_file(&io, true) { + match rudderc::compile::compile(&io, true) { Ok(_) => { println!( "{}: compilation of {}", diff --git a/rudder-lang/tools/rudderc-dev.conf b/rudder-lang/tools/rudderc-dev.conf index 759aba64f76..3b1396c4b01 100644 --- a/rudder-lang/tools/rudderc-dev.conf +++ b/rudder-lang/tools/rudderc-dev.conf @@ -7,13 +7,20 @@ alt_cfengine_methods="repos/dsc/plugin/ncf/30_generic_methods/" dsc_methods="repos/dsc/packaging/Files/share/initial-policy/ncf/30_generic_methods/" [compile] -source="tests/techniques/" -dest="tests/techniques/" -format="cfengine" +input="tests/techniques/" +output="tests/techniques/" -[translate] -source="tests/techniques/" -dest="tests/techniques/" +[migrate] +input="tests/techniques/" +output="tests/techniques/" + +[technique_read] +input="tests/techniques/" +output="tests/techniques/" + +[technique_generate] +input="tests/techniques/" +output="tests/techniques/" [testing_loop] cfengine="/opt/rudder/bin/cf-promises" diff --git a/rudder-lang/tools/rudderc-prod.conf b/rudder-lang/tools/rudderc-prod.conf index a45bfaaf45b..288b86e61bb 100644 --- a/rudder-lang/tools/rudderc-prod.conf +++ b/rudder-lang/tools/rudderc-prod.conf @@ -1,17 +1,26 @@ [shared] -#stdlib="/opt/rudder/share/rudder-lang/lib/" +stdlib ="/opt/rudder/share/rudder-lang/lib/" #cfengine_methods="/usr/share/ncf/tree/30_generic_methods/" #alt_cfengine_methods="/var/rudder/configuration-repository/ncf/30_generic_methods" #dsc_methods="/var/rudder/configuration-repository/dsc/ncf/30_generic_methods/" +# todo update path, probably erroned [compile] -source="/var/rudder/configuration-repository/techniques/" # must be appened with ${technique_category}/${technique_name}.${source_format} -dest="/tmp/rudderc/tester/" -format="cfengine" +input="/var/rudder/configuration-repository/techniques/ncf_techniques/" +output="/tmp/rudderc/tester/" + +[migrate] +input="/tmp/rudderc/tester/" +output="/tmp/rudderc/tester/" + +[technique_read] +input="/var/rudder/configuration-repository/techniques/ncf_techniques/" +output="/tmp/rudderc/tester/" + +[technique_generate] +input="/var/rudder/configuration-repository/techniques/ncf_techniques/" +output="/tmp/rudderc/tester/" -[translate] -source="/tmp/rudderc/tester/" -dest="/tmp/rudderc/tester/" [testing_loop] cfengine="/opt/rudder/bin/cf-promises"