diff --git a/rudder-lang/libs/resourcelib.rl b/rudder-lang/libs/resourcelib.rl index 73bfc95c6bc..97303c9e2c6 100644 --- a/rudder-lang/libs/resourcelib.rl +++ b/rudder-lang/libs/resourcelib.rl @@ -1,187 +1,537 @@ @format=0 -resource package(p0) -resource group(p0) +resource directory(p0) +resource variable(p0, p1) +resource user(p0) +resource command(p0) +resource http_request(p0, p1) resource service(p0) -resource variable(p0,p1) +resource kernel_module(p0) +resource group(p0) resource condition(p0) -resource directory(p0) -resource schedule(p0) resource file(p0) -resource monitoring(p0) -resource http_request(p0,p1) -resource user(p0) +resource schedule(p0) +resource sysctl(p0) resource environment(p0) -resource sharedfile(p0,p1) +resource monitoring(p0) +resource sharedfile(p0, p1) resource permissions(p0) -resource command(p0) -resource kernel_module(p0) +resource package(p0) + +@class_parameters={"command": 0} +command state execution() {} + +@class_parameters={"command": 0} +command state execution_once(ok_codes, until, unique_id) {} + +@class_parameters={"command": 0} +command state execution_result(kept_codes, repaired_codes) {} + +@class_parameters={"condition_prefix": 0} +condition state from_command(command, true_codes, false_codes) {} + +@class_parameters={"condition_prefix": 0} +condition state from_expression(condition_expression) {} + +@class_parameters={"condition_prefix": 0} +condition state from_expression_persistent(condition_expression, duration) {} + +@class_parameters={"condition_prefix": 0} +condition state from_variable_existence(variable_name) {} + +@class_parameters={"condition_prefix": 0} +condition state from_variable_match(variable_name, expected_match) {} + +@class_parameters={"condition": 0} +condition state once() {} + +@class_parameters={"target": 0} +directory state absent(recursive) {} + +@class_parameters={"directory_name": 0} +directory state check_exists() {} + +@class_parameters={"target": 0} +directory state create() {} + +@class_parameters={"target": 0} +directory state present() {} + +@class_parameters={"name": 0} +environment state variable_present(value) {} + +@class_parameters={"target": 0} +file state absent() {} + +@class_parameters={"variable_name": 1} +file state augeas_commands(variable_prefix, commands, autoload) {} + +@class_parameters={"lens": 2} +file state augeas_set(path, value, file) {} + +@class_parameters={"file": 0} +file state block_present(block) {} + +@class_parameters={"file": 0} +file state block_present_in_section(section_start, section_end, block) {} + +@class_parameters={"file_name": 0} +file state check_block_device() {} + +@class_parameters={"file_name": 0} +file state check_character_device() {} + +@class_parameters={"file_name": 0} +file state check_exists() {} + +@class_parameters={"file_name": 0} +file state check_FIFO_pipe() {} + +@class_parameters={"file_name_1": 0} +file state check_hardlink(file_name_2) {} + +@class_parameters={"file_name": 0} +file state check_regular() {} + +@class_parameters={"file_name": 0} +file state check_socket() {} + +@class_parameters={"file_name": 0} +file state check_symlink() {} + +@class_parameters={"symlink": 0} +file state check_symlinkto(target) {} + +@class_parameters={"file": 0} +file state content(lines, enforce) {} + +@class_parameters={"destination": 1} +file state copy_from_local_source(source) {} + +@class_parameters={"destination": 1} +file state copy_from_local_source_recursion(source, recursion) {} + +@class_parameters={"destination": 1} +file state copy_from_local_source_with_check(source, check_command, rc_ok) {} + +@class_parameters={"destination": 1} +file state copy_from_remote_source(source) {} + +@class_parameters={"destination": 1} +file state copy_from_remote_source_recursion(source, recursion) {} + +@class_parameters={"target": 0} +file state create() {} + +@class_parameters={"destination": 1} +file state create_symlink(source) {} + +@class_parameters={"destination": 1} +file state create_symlink_enforce(source, enforce) {} + +@class_parameters={"destination": 1} +file state create_symlink_force(source) {} + +@class_parameters={"destination": 1} +file state download(source) {} + +@class_parameters={"file": 0} +file state enforce_content(lines, enforce) {} + +@class_parameters={"file": 0} +file state ensure_block_in_section(section_start, section_end, block) {} + +@class_parameters={"file": 0} +file state ensure_block_present(block) {} + +@class_parameters={"file": 0} +file state ensure_key_value(key, value, separator) {} + +@class_parameters={"file": 0} +file state ensure_key_value_option(key, value, option, separator) {} + +@class_parameters={"file": 0} +file state ensure_key_value_parameter_in_list(key, key_value_separator, parameter, parameter_separator, leading_char_separator, closing_char_separator) {} + +@class_parameters={"file": 0} +file state ensure_key_value_parameter_not_in_list(key, key_value_separator, parameter_regex, parameter_separator, leading_char_separator, closing_char_separator) {} + +@class_parameters={"file": 0} +file state ensure_key_value_present_in_ini_section(section, name, value) {} + +@class_parameters={"file": 0} +file state ensure_keys_values(keys, separator) {} + +@class_parameters={"file": 0} +file state ensure_line_present_in_ini_section(section, line) {} + +@class_parameters={"file": 0} +file state ensure_line_present_in_xml_tag(tag, line) {} + +@class_parameters={"file": 0} +file state ensure_lines_absent(lines) {} + +@class_parameters={"file": 0} +file state ensure_lines_present(lines) {} + +@class_parameters={"destination": 1} +file state from_http_server(source) {} + +@class_parameters={"destination": 1} +file state from_local_source(source) {} + +@class_parameters={"destination": 1} +file state from_local_source_recursion(source, recursion) {} + +@class_parameters={"destination": 1} +file state from_local_source_with_check(source, check_command, rc_ok) {} + +@class_parameters={"destination": 1} +file state from_remote_source(source) {} + +@class_parameters={"destination": 1} +file state from_remote_source_recursion(source, recursion) {} + +@class_parameters={"destination": 1} +file state from_shared_folder(source, hash_type) {} + +@class_parameters={"destination": 1} +file state from_string_mustache(template) {} + +@class_parameters={"destination": 1} +file state from_template(source_template) {} + +@class_parameters={"destination": 1} +file state from_template_jinja2(source_template) {} + +@class_parameters={"destination": 1} +file state from_template_mustache(source_template) {} + +@class_parameters={"destination": 1} +file state from_template_type(source_template, template_type) {} + +@class_parameters={"file": 0} +file state key_value_parameter_absent_in_list(key, key_value_separator, parameter_regex, parameter_separator, leading_char_separator, closing_char_separator) {} + +@class_parameters={"file": 0} +file state key_value_parameter_present_in_list(key, key_value_separator, parameter, parameter_separator, leading_char_separator, closing_char_separator) {} + +@class_parameters={"file": 0} +file state key_value_present(key, value, separator) {} + +@class_parameters={"file": 0} +file state key_value_present_in_ini_section(section, name, value) {} + +@class_parameters={"file": 0} +file state key_value_present_option(key, value, separator, option) {} + +@class_parameters={"file": 0} +file state keys_values_present(keys, separator) {} + +@class_parameters={"file": 0} +file state line_present_in_ini_section(section, line) {} + +@class_parameters={"file": 0} +file state line_present_in_xml_tag(tag, line) {} + +@class_parameters={"file": 0} +file state lines_absent(lines) {} + +@class_parameters={"file": 0} +file state lines_present(lines) {} + +@class_parameters={"target": 0} +file state present() {} + +@class_parameters={"target": 0} +file state remove() {} + +@class_parameters={"file": 0} +file state replace_lines(line, replacement) {} + +@class_parameters={"target": 0} +file state report_content(regex, context) {} + +@class_parameters={"target": 0} +file state report_content_head(limit) {} + +@class_parameters={"target": 0} +file state report_content_tail(limit) {} + +@class_parameters={"destination": 1} +file state symlink_present(source) {} + +@class_parameters={"destination": 1} +file state symlink_present_force(source) {} + +@class_parameters={"destination": 1} +file state symlink_present_option(source, enforce) {} + +@class_parameters={"target_file": 1} +file state template_expand(tml_file, mode, owner, group) {} + +@class_parameters={"group": 0} +group state absent() {} + +@class_parameters={"group": 0} +group state present() {} + +@class_parameters={"url": 1} +http_request state check_status_headers(method, expected_status, headers) {} + +@class_parameters={"url": 1} +http_request state content_headers(method, content, headers) {} + +@class_parameters={"module_name": 0} +kernel_module state configuration(configuration) {} + +@class_parameters={"module_name": 0} +kernel_module state enabled_at_boot() {} + +@class_parameters={"module_name": 0} +kernel_module state loaded() {} + +@class_parameters={"module_name": 0} +kernel_module state not_loaded() {} + +@class_parameters={"key": 0} +monitoring state parameter(value) {} + +@class_parameters={"template": 0} +monitoring state template() {} + +@class_parameters={"name": 0} +package state absent(version, architecture, provider) {} + +@class_parameters={"package_name": 0} +package state check_installed() {} + +@class_parameters={"package_name": 0} +package state install() {} + +@class_parameters={"package_name": 0} +package state install_version(package_version) {} + +@class_parameters={"package_name": 0} +package state install_version_cmp(version_comparator, package_version, action) {} + +@class_parameters={"package_name": 0} +package state install_version_cmp_update(version_comparator, package_version, action, update_policy) {} + +@class_parameters={"name": 0} +package state present(version, architecture, provider) {} + +@class_parameters={"package_name": 0} +package state remove() {} + +@class_parameters={"name": 0} +package state state(version, architecture, provider, state) {} + +@class_parameters={"name": 0} +package state state_options(version, architecture, provider, state, options) {} + +@class_parameters={"package_name": 0} +package state verify() {} + +@class_parameters={"package_name": 0} +package state verify_version(package_version) {} + +@class_parameters={"path": 0} +permissions state acl_entry(recursive, user, group, other) {} + +@class_parameters={"path": 0} +permissions state dirs(mode, owner, group) {} + +@class_parameters={"path": 0} +permissions state dirs_recurse(mode, owner, group) {} + +@class_parameters={"path": 0} +permissions state dirs_recursive(mode, owner, group) {} + +@class_parameters={"path": 0} +permissions state group_acl_absent(recursive, group) {} + +@class_parameters={"path": 0} +permissions state group_acl_present(recursive, group, ace) {} + +@class_parameters={"path": 0} +permissions state other_acl_present(recursive, other) {} + +@class_parameters={"path": 0} +permissions state posix_acls_absent(recursive) {} + +@class_parameters={"path": 0} +permissions state recurse(mode, owner, group) {} + +@class_parameters={"path": 0} +permissions state recursive(mode, owner, group) {} + +@class_parameters={"path": 0} +permissions state type_recursion(mode, owner, group, type, recursion) {} + +@class_parameters={"path": 0} +permissions state user_acl_absent(recursive, user) {} + +@class_parameters={"path": 0} +permissions state user_acl_present(recursive, user, ace) {} + +@class_parameters={"job_id": 0} +schedule state simple(agent_periodicity, max_execution_delay_minutes, max_execution_delay_hours, start_on_minutes, start_on_hours, start_on_day_of_week, periodicity_minutes, periodicity_hours, periodicity_days, mode) {} + +@class_parameters={"job_id": 0} +schedule state simple_catchup(agent_periodicity, max_execution_delay_minutes, max_execution_delay_hours, start_on_minutes, start_on_hours, start_on_day_of_week, periodicity_minutes, periodicity_hours, periodicity_days) {} + +@class_parameters={"job_id": 0} +schedule state simple_nodups(agent_periodicity, max_execution_delay_minutes, max_execution_delay_hours, start_on_minutes, start_on_hours, start_on_day_of_week, periodicity_minutes, periodicity_hours, periodicity_days) {} + +@class_parameters={"job_id": 0} +schedule state simple_stateless(agent_periodicity, max_execution_delay_minutes, max_execution_delay_hours, start_on_minutes, start_on_hours, start_on_day_of_week, periodicity_minutes, periodicity_hours, periodicity_days) {} + +@class_parameters={"service_name": 0} +service state action(action) {} + +@class_parameters={"service_name": 0} +service state check_disabled_at_boot() {} + +@class_parameters={"service_name": 0} +service state check_running() {} + +@class_parameters={"service_regex": 0} +service state check_running_ps() {} + +@class_parameters={"service_name": 0} +service state check_started_at_boot() {} + +@class_parameters={"service_name": 0} +service state disabled() {} + +@class_parameters={"service_name": 0} +service state enabled() {} + +@class_parameters={"service_name": 0} +service state ensure_disabled_at_boot() {} + +@class_parameters={"service_name": 0} +service state ensure_running() {} + +@class_parameters={"service_name": 0} +service state ensure_running_path(service_path) {} + +@class_parameters={"service_name": 0} +service state ensure_started_at_boot() {} + +@class_parameters={"service_name": 0} +service state ensure_stopped() {} + +@class_parameters={"service_name": 0} +service state reload() {} + +@class_parameters={"service_name": 0} +service state restart() {} + +@class_parameters={"service_name": 0} +service state restart_if(trigger_class) {} + +@class_parameters={"service_name": 0} +service state start() {} + +@class_parameters={"service_name": 0} +service state started() {} + +@class_parameters={"service_name": 0} +service state started_path(service_path) {} + +@class_parameters={"service_name": 0} +service state stop() {} + +@class_parameters={"service_name": 0} +service state stopped() {} + +@class_parameters={"file_id": 1} +sharedfile state from_node(source_uuid, file_path) {} + +@class_parameters={"file_id": 1} +sharedfile state to_node(target_uuid, file_path, ttl) {} + +@class_parameters={"key": 0} +sysctl state value(value, filename, option) {} + +@class_parameters={"login": 0} +user state absent() {} + +@class_parameters={"login": 0} +user state create(description, home, group, shell, locked) {} + +@class_parameters={"login": 0} +user state fullname(fullname) {} + +@class_parameters={"user": 0} +user state group(group_name) {} + +@class_parameters={"login": 0} +user state home(home) {} + +@class_parameters={"login": 0} +user state locked() {} + +@class_parameters={"login": 0} +user state password_hash(password) {} + +@class_parameters={"login": 0} +user state present() {} + +@class_parameters={"login": 0} +user state primary_group(primary_group) {} + +@class_parameters={"login": 0} +user state shell(shell) {} + +@class_parameters={"login": 0} +user state uid(uid) {} + +@class_parameters={"variable_name": 1} +variable state dict(variable_prefix, value) {} + +@class_parameters={"variable_name": 1} +variable state dict_from_file(variable_prefix, file_name) {} + +@class_parameters={"variable_name": 1} +variable state dict_from_file_type(variable_prefix, file_name, file_type) {} + +@class_parameters={"variable_name": 1} +variable state dict_from_osquery(variable_prefix, query) {} + +@class_parameters={"variable_name": 1} +variable state dict_merge(variable_prefix, first_variable, second_variable) {} + +@class_parameters={"variable_name": 1} +variable state dict_merge_tolerant(variable_prefix, first_variable, second_variable) {} + +@class_parameters={"variable_name": 1} +variable state iterator(variable_prefix, value, separator) {} + +@class_parameters={"variable_name": 1} +variable state iterator_from_file(variable_prefix, file_name, separator_regex, comments_regex) {} + +@class_parameters={"variable_name": 1} +variable state string(variable_prefix, value) {} + +@class_parameters={"variable_name": 1} +variable state string_default(variable_prefix, source_variable, default_value) {} + +@class_parameters={"variable_name": 1} +variable state string_from_augeas(variable_prefix, path, lens, file) {} + +@class_parameters={"variable_name": 1} +variable state string_from_command(variable_prefix, command) {} + +@class_parameters={"variable_name": 1} +variable state string_from_file(variable_prefix, file_name) {} + +@class_parameters={"variable_name": 1} +variable state string_from_math_expression(variable_prefix, expression, format) {} + +@class_parameters={"variable_name": 0} +variable state string_match(expected_match) {} -command state execution(){} -command state execution_once(p1,p2,p3){} -command state execution_result(p1,p2){} -condition state from_command(p1,p2,p3){} -condition state from_expression(p1){} -condition state from_expression_persistent(p1,p2){} -condition state from_variable_existence(p1){} -condition state from_variable_match(p1,p2){} -condition state once(){} -directory state absent(p1){} -directory state check_exists(){} -directory state create(){} -directory state present(){} -environment state variable_present(p1){} -file state absent(){} -file state block_present(p1){} -file state block_present_in_section(p1,p2,p3){} -file state check_block_device(){} -file state check_character_device(){} -file state check_exists(){} -file state check_FIFO_pipe(){} -file state check_hardlink(p1){} -file state check_regular(){} -file state check_socket(){} -file state check_symlink(){} -file state check_symlinkto(p1){} -file state content(p1,p2){} -file state copy_from_local_source(p1){} -file state copy_from_local_source_recursion(p1,p2){} -file state copy_from_local_source_with_check(p1,p2,p3){} -file state copy_from_remote_source(p1){} -file state copy_from_remote_source_recursion(p1,p2){} -file state create(){} -file state create_symlink(p1){} -file state create_symlink_enforce(p1,p2){} -file state create_symlink_force(p1){} -file state download(p1){} -file state enforce_content(p1,p2){} -file state ensure_block_in_section(p1,p2,p3){} -file state ensure_block_present(p1){} -file state ensure_key_value(p1,p2,p3){} -file state ensure_key_value_option(p1,p2,p3,p4){} -file state ensure_key_value_parameter_in_list(p1,p2,p3,p4,p5,p6){} -file state ensure_key_value_parameter_not_in_list(p1,p2,p3,p4,p5,p6){} -file state ensure_key_value_present_in_ini_section(p1,p2,p3){} -file state ensure_keys_values(p1,p2){} -file state ensure_line_present_in_ini_section(p1,p2){} -file state ensure_line_present_in_xml_tag(p1,p2){} -file state ensure_lines_absent(p1){} -file state ensure_lines_present(p1){} -file state from_http_server(p1){} -file state from_local_source(p1){} -file state from_local_source_recursion(p1,p2){} -file state from_local_source_with_check(p1,p2,p3){} -file state from_remote_source(p1){} -file state from_remote_source_recursion(p1,p2){} -file state from_shared_folder(p1,p2){} -file state from_string_mustache(p1){} -file state from_template(p1){} -file state from_template_jinja2(p1){} -file state from_template_mustache(p1){} -file state from_template_type(p1,p2){} -file state key_value_parameter_absent_in_list(p1,p2,p3,p4,p5,p6){} -file state key_value_parameter_present_in_list(p1,p2,p3,p4,p5,p6){} -file state key_value_present(p1,p2,p3){} -file state key_value_present_in_ini_section(p1,p2,p3){} -file state key_value_present_option(p1,p2,p3,p4){} -file state keys_values_present(p1,p2){} -file state line_present_in_ini_section(p1,p2){} -file state line_present_in_xml_tag(p1,p2){} -file state lines_absent(p1){} -file state lines_present(p1){} -file state present(){} -file state remove(){} -file state replace_lines(p1,p2){} -file state report_content(p1,p2){} -file state report_content_head(p1){} -file state report_content_tail(p1){} -file state symlink_present(p1){} -file state symlink_present_force(p1){} -file state symlink_present_option(p1,p2){} -file state template_expand(p1,p2,p3,p4){} -group state absent(){} -group state present(){} -http_request state check_status_headers(p2,p3){} -http_request state content_headers(p2,p3){} -kernel_module state configuration(p1){} -kernel_module state enabled_at_boot(){} -kernel_module state loaded(){} -kernel_module state not_loaded(){} -monitoring state parameter(p1){} -monitoring state template(){} -package state absent(p1,p2,p3){} -package state check_installed(){} -package state install(){} -package state install_version(p1){} -package state install_version_cmp(p1,p2,p3){} -package state install_version_cmp_update(p1,p2,p3,p4){} -package state present(p1,p2,p3){} -package state remove(){} -package state state(p1,p2,p3,p4){} -package state state_options(p1,p2,p3,p4,p5){} -package state verify(){} -package state verify_version(p1){} -permissions state acl_entry(p1,p2,p3,p4){} -permissions state dirs(p1,p2,p3){} -permissions state dirs_recurse(p1,p2,p3){} -permissions state dirs_recursive(p1,p2,p3){} -permissions state group_acl_absent(p1,p2){} -permissions state group_acl_present(p1,p2,p3){} -permissions state other_acl_present(p1,p2){} -permissions state posix_acls_absent(p1){} -permissions state recurse(p1,p2,p3){} -permissions state recursive(p1,p2,p3){} -permissions state type_recursion(p1,p2,p3,p4,p5){} -permissions state user_acl_absent(p1,p2){} -permissions state user_acl_present(p1,p2,p3){} -schedule state simple(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10){} -schedule state simple_catchup(p1,p2,p3,p4,p5,p6,p7,p8,p9){} -schedule state simple_nodups(p1,p2,p3,p4,p5,p6,p7,p8,p9){} -schedule state simple_stateless(p1,p2,p3,p4,p5,p6,p7,p8,p9){} -service state action(p1){} -service state check_disabled_at_boot(){} -service state check_running(){} -service state check_running_ps(){} -service state check_started_at_boot(){} -service state disabled(){} -service state enabled(){} -service state ensure_disabled_at_boot(){} -service state ensure_running(){} -service state ensure_running_path(p1){} -service state ensure_started_at_boot(){} -service state ensure_stopped(){} -service state reload(){} -service state restart(){} -service state restart_if(p1){} -service state start(){} -service state started(){} -service state started_path(p1){} -service state stop(){} -service state stopped(){} -sharedfile state from_node(p2){} -sharedfile state to_node(p2,p3){} -user state absent(){} -user state create(p1,p2,p3,p4,p5){} -user state fullname(p1){} -user state home(p1){} -user state locked(){} -user state password_hash(p1){} -user state present(){} -user state primary_group(p1){} -user state shell(p1){} -user state uid(p1){} -variable state dict(p2){} -variable state dict_from_file(p2){} -variable state dict_from_file_type(p2,p3){} -variable state dict_from_osquery(p2){} -variable state dict_merge(p2,p3){} -variable state dict_merge_tolerant(p2,p3){} -variable state iterator(p2,p3){} -variable state iterator_from_file(p2,p3,p4){} -variable state string(p2){} -variable state string_default(p2,p3){} -variable state string_from_command(p2){} -variable state string_from_file(p2){} -variable state string_from_math_expression(p2,p3){} -variable state string_match(){} diff --git a/rudder-lang/src/generators.rs b/rudder-lang/src/generators.rs index 9731e2cea6c..c87b19738e3 100644 --- a/rudder-lang/src/generators.rs +++ b/rudder-lang/src/generators.rs @@ -2,8 +2,10 @@ // SPDX-FileCopyrightText: 2019-2020 Normation SAS mod cfengine; +mod dsc; pub use self::cfengine::CFEngine; +pub use self::dsc::DSC; use crate::ast::AST; use crate::error::*; use serde::de::{self, Deserialize, Deserializer}; @@ -26,10 +28,10 @@ pub trait Generator { ) -> Result<()>; } -pub fn new_generator(format: &Format) -> Result { +pub fn new_generator(format: &Format) -> Result> { match format { - Format::CFEngine => Ok(CFEngine::new()), - // Format::DSC => Ok(DSC::new()), + Format::CFEngine => Ok(Box::new(CFEngine::new())), + Format::DSC => Ok(Box::new(DSC::new())), // Format::JSON => Ok(JSON::new()), _ => Err(Error::User(format!("No Generator for {} format", format))), } @@ -50,7 +52,7 @@ impl fmt::Display for Format { "{}", match self { Format::CFEngine => "cf", - Format::DSC => "dsc", + Format::DSC => "ps1", Format::RudderLang => "rl", Format::JSON => "json", } @@ -64,7 +66,7 @@ impl FromStr for Format { fn from_str(format: &str) -> Result { match format { "cf" | "cfengine" => Ok(Format::CFEngine), - "dsc" => Ok(Format::DSC), + "dsc" | "ps1" => Ok(Format::DSC), "json" => Ok(Format::JSON), "rl" => Ok(Format::RudderLang), // RudderLang is an error, not a compilation format diff --git a/rudder-lang/src/generators/cfengine.rs b/rudder-lang/src/generators/cfengine.rs index caff86f969e..2cc13ef6297 100644 --- a/rudder-lang/src/generators/cfengine.rs +++ b/rudder-lang/src/generators/cfengine.rs @@ -28,8 +28,8 @@ pub struct CFEngine { } impl CFEngine { - pub fn new() -> CFEngine { - CFEngine { + pub fn new() -> Self { + Self { current_cases: Vec::new(), var_prefixes: HashMap::new(), prefixes: HashMap::new(), @@ -54,20 +54,17 @@ impl CFEngine { fn parameter_to_cfengine(&mut self, param: &Value) -> Result { Ok(match param { - Value::String(s) => - // TODO variable reinterpret (rudlang systemvar to cfengine systemvar) - { - "\"".to_owned() - + s.format( - |x: &str| { - x.replace("\\", "\\\\") // backslash escape - .replace("\"", "\\\"") // quote escape - .replace("$", "${const.dollar}") - }, // dollar escape - |y: &str| "${".to_owned() + y + "}", // variable inclusion - ) - .as_str() - + "\"" + Value::String(s) => { + // TODO variable reinterpret (rudlang systemvar to cfengine systemvar) + let formatted_param = s.format( + |x: &str| { + x.replace("\\", "\\\\") // backslash escape + .replace("\"", "\\\"") // quote escape + .replace("$", "${const.dollar}") + }, // dollar escape + |y: &str| format!("${{{}}}", y), // variable inclusion + ); + format!(r#""{}""#, formatted_param) } Value::Number(_, _) => unimplemented!(), Value::Boolean(_, _) => unimplemented!(), @@ -369,7 +366,7 @@ impl CFEngine { let mut metadatas = String::new(); let mut push_metadata = |entry: &str| { if let Some(val) = map.remove(entry) { - metadatas.push_str(&format!("# @{} {}\n", entry, val)); + metadatas.push_str(&format!("# @{} {:#?}\n", entry, val)); } }; push_metadata("name"); @@ -406,8 +403,9 @@ impl Generator for CFEngine { None => continue, }; self.reset_context(); - let mut content = "# generated by rudder-lang\n".to_owned(); - let fileinfo = match files.get(&file_to_create) { + + // get header + let header = match files.get(&file_to_create) { Some(s) => s.to_string(), None => { if technique_metadata { @@ -417,46 +415,61 @@ impl Generator for CFEngine { } } }; - content.push_str(&fileinfo); - let mut params = res + + // get parameters + let mut parameters = res .parameters .iter() .chain(state.parameters.iter()) .map(|p| p.name.fragment()) .collect::>() .join(","); - if !params.is_empty() { - params = format!("({})", params); + if !parameters.is_empty() { + parameters = format!("({})", parameters); } - content.push_str(&format!( - "bundle agent {}_{}{}\n", - rn.fragment(), - sn.fragment(), - params - )); - content.push_str( - "{\n vars:\n \"resources_dir\" string => \"${this.promise_dirname}/resources\";\n" + + // get methods + let methods: String = state + .statements + .iter() + .enumerate() + .map(|(i, st)| { + self.format_statement(gc, st, i, "any".to_string(), generic_methods) + }) + .collect::>>()? + .join("\n"); + + // merge header + parameters + methods with technique file body + let content = format!( + r#"# generated by rudder-lang +{header} + +bundle agent {resource_name}_{state_name}{parameters} +{{ + vars: + "resources_dir" string => "${{this.promise_dirname}}/resources"; + + methods: +{methods} +}}"#, + header = header, + resource_name = rn.fragment(), + state_name = sn.fragment(), + parameters = parameters, + methods = methods ); - content.push_str(" methods:\n"); - for (i, st) in state.statements.iter().enumerate() { - content.push_str(&self.format_statement( - gc, - st, - i, - "any".to_string(), - generic_methods, - )?); - } - content.push_str("}\n"); files.insert(file_to_create, content); } } + // 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::User("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()) diff --git a/rudder-lang/src/generators/dsc.rs b/rudder-lang/src/generators/dsc.rs new file mode 100644 index 00000000000..b8bbc8e2130 --- /dev/null +++ b/rudder-lang/src/generators/dsc.rs @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2019-2020 Normation SAS + +use super::Generator; +use crate::ast::enums::EnumExpression; +use crate::ast::resource::*; +use crate::ast::value::*; +use crate::ast::*; +use crate::parser::*; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use crate::error::*; + +/* + DSC parameter types: + + [int] 32-bit signed integer => no match + [long] 64-bit signed integer => no match + [string] Fixed-length string of Unicode chars => corresponds to our String type + [char] Unicode 16-bit character => no match + [bool] True/false value => corresponds to our Boolean type + [byte] 8-bit unsigned integer => no match + [double] Double-precision 64-bit fp numbers => corresponds to our Number type + [decimal] 128-bit decimal value => no match + [single] Single-precision 32-bit fp numbers => no match + [array] Array of values => + [xml] Xmldocument object => no match + [hashtable] Hashtable object (~Dictionary~) => +*/ + +pub struct DSC { + // list of already formatted expression in current case + current_cases: Vec, + // match enum local variables with class prefixes + var_prefixes: HashMap, + // already used class prefix + prefixes: HashMap, + // condition to add for every other condition for early return + return_condition: Option, +} + +impl DSC { + pub fn new() -> Self { + Self { + current_cases: Vec::new(), + var_prefixes: HashMap::new(), + prefixes: HashMap::new(), + return_condition: None, + } + } + + fn new_var(&mut self, prefix: &str) { + let id = self.prefixes.get(prefix).unwrap_or(&0) + 1; + self.prefixes.insert(prefix.to_string(), id); + let var = format!("{}{}", prefix, id); + self.var_prefixes.insert(prefix.to_string(), var); + } + fn reset_cases(&mut self) { + // TODO this make case in case fail + self.current_cases = Vec::new(); + } + fn reset_context(&mut self) { + self.var_prefixes = HashMap::new(); + self.return_condition = None; + } + + fn parameter_to_dsc(&self, param: &Value, param_name: &str) -> Result { + Ok(match param { + Value::String(s) => { + // TODO integrate name to parameters + let param_value = s.format( + |x: &str| { + x.replace("\\", "\\\\") // backslash escape + .replace("\"", "\\\"") // quote escape + .replace("$", "${const.dollar}") + }, // dollar escape + |y: &str| format!("${{{}}}", y), // variable inclusion + ); + format!(r#"-{} "{}""#, param_name, param_value) + } + Value::Number(_, _) => unimplemented!(), + Value::Boolean(_, _) => unimplemented!(), + Value::EnumExpression(_e) => "".into(), // TODO + Value::List(_) => unimplemented!(), + Value::Struct(_) => unimplemented!(), + }) + } + + fn format_case_expr(&mut self, gc: &AST, case: &EnumExpression) -> Result { + Ok(match case { + EnumExpression::And(e1, e2) => { + let mut lexpr = self.format_case_expr(gc, e1)?; + let mut rexpr = self.format_case_expr(gc, e2)?; + if lexpr.contains("|") { + lexpr = format!("({})", lexpr); + } + if rexpr.contains("|") { + rexpr = format!("({})", rexpr); + } + format!("{}.{}", lexpr, rexpr) + } + EnumExpression::Or(e1, e2) => format!( + "{}|{}", + self.format_case_expr(gc, e1)?, + self.format_case_expr(gc, e2)? + ), + // TODO what about classes that have not yet been set ? can it happen ? + EnumExpression::Not(e1) => { + let mut expr = self.format_case_expr(gc, e1)?; + if expr.contains("|") || expr.contains("&") { + expr = format!("!({})", expr); + } + format!("!{}", expr) + } + EnumExpression::Compare(var, e, item) => { + if let Some(true) = gc.enum_list.enum_is_global(*e) { + // We probably need some translation here since not all enums are available in cfengine (ex debian_only) + item.fragment().to_string() // here + } else { + // concat var name + item + let prefix = &self.var_prefixes[var.fragment()]; + // TODO there may still be some conflicts with var or enum containing '_' + format!("{}_{}_{}", prefix, e.fragment(), item.fragment()) + } + } + EnumExpression::RangeCompare(_var, _e, _item1, _item2) => unimplemented!(), // TODO + EnumExpression::Default(_) => { + // extract current cases and build an opposite expression + if self.current_cases.is_empty() { + "any".to_string() + } else { + format!("!({})", self.current_cases.join("|")) + } + } + EnumExpression::NoDefault(_) => "".to_string(), + }) + } + + fn get_method_parameters(&self, gc: &AST, state_decl: &StateDeclaration) -> Result { + // depending on whether class_parameters should only be used for generic_methods or not + // might better handle relative errors as panic! rather than Error::User + + let state_def = match gc.resources.get(&state_decl.resource) { + Some(r) => match r.states.get(&state_decl.state) { + Some(s) => s, + None => panic!( + "No method relies on the \"{}\" state for \"{}\"", + state_decl.state.fragment(), + state_decl.resource.fragment() + ), + }, + None => panic!( + "No method relies on the \"{}\" resource", + state_decl.resource.fragment() + ), + }; + + let mut param_names = state_def + .parameters + .iter() + .map(|p| p.name.fragment()) + .collect::>(); + + let mut class_param_names = match state_def.metadata.get(&Token::from("class_parameters")) { + Some(Value::Struct(parameters)) => parameters + .iter() + .map(|p| { + let p_index = match p.1 { + Value::Number(_, n) => *n as usize, + _ => { + return Err(Error::User(String::from( + "Expected value type for class parameters metadata: Number", + ))) + } + }; + Ok((p.0.as_ref(), p_index)) + }) + .collect::>>()?, + _ => { + // swap to info! if class_parameter for all methods is a thing + debug!( + "The {}_{} method has no class_parameters metadata attached", + state_decl.state.fragment(), + state_decl.resource.fragment() + ); + Vec::new() + } + }; + class_param_names.sort_by(|a, b| a.1.cmp(&b.1)); + + for (name, index) in class_param_names { + if index <= param_names.len() { + param_names.insert(index, name); + } else { + return Err(Error::User(String::from( + "Class parameter indexes are out of method bounds", + ))); + } + } + + // TODO setup mode and output var by calling ... bundle + map_strings_results( + state_decl + .resource_params + .iter() + .chain(state_decl.state_params.iter()) + .enumerate(), + |(i, x)| self.parameter_to_dsc(x, param_names.get(i).unwrap_or(&&"unnamed")), + " ", + ) + } + + // TODO simplify expression and remove useless conditions for more readable cfengine + // TODO underscore escapement + // TODO how does cfengine use utf8 + // TODO variables + // TODO comments and metadata + // TODO use in_class everywhere + fn format_statement(&mut self, gc: &AST, st: &Statement) -> Result { + match st { + Statement::StateDeclaration(sd) => { + if let Some(var) = sd.outcome { + self.new_var(&var); + } + let component = match sd.metadata.get(&"component".into()) { + // TODO use static_to_string + Some(Value::String(s)) => match &s.data[0] { + PInterpolatedElement::Static(st) => st.clone(), + _ => "any".to_string(), + }, + _ => "any".to_string(), + }; + + Ok(format!( + r#" $local_classes = Merge-ClassContext $local_classes $({} {} -componentName "{}" -reportId $reportId -techniqueName $techniqueName -auditOnly:$auditOnly).get_item("classes")"#, + pascebab_case(&component), + self.get_method_parameters(gc, sd)?, + component, + )) + } + Statement::Case(_case, vec) => { + self.reset_cases(); + map_strings_results( + vec.iter(), + |(_case, vst)| { + // TODO case in case + // let case_exp = self.format_case_expr(gc, case)?; + map_strings_results(vst.iter(), |st| self.format_statement(gc, st), "") + }, + "", + ) + } + Statement::Fail(msg) => Ok(format!( + " \"method_call\" usebundle => ncf_fail({});\n", + self.parameter_to_dsc(msg, "Fail")? + )), + Statement::Log(msg) => Ok(format!( + " \"method_call\" usebundle => ncf_log({});\n", + self.parameter_to_dsc(msg, "Log")? + )), + Statement::Return(outcome) => { + // handle end of bundle + self.return_condition = Some(match self.current_cases.last() { + None => "!any".into(), + Some(c) => format!("!({})", c), + }); + Ok(if *outcome == Token::new("", "kept") { + " \"method_call\" usebundle => success();\n".into() + } else if *outcome == Token::new("", "repaired") { + " \"method_call\" usebundle => repaired();\n".into() + } else { + " \"method_call\" usebundle => error();\n".into() + }) + } + Statement::Noop => Ok(String::new()), + // TODO Statement::VariableDefinition() + _ => Ok(String::new()), + } + } + + fn value_to_string(&mut self, value: &Value, string_delim: bool) -> Result { + let delim = if string_delim { "\"" } else { "" }; + Ok(match value { + Value::String(s) => format!( + "{}{}{}", + delim, + s.data + .iter() + .map(|t| match t { + PInterpolatedElement::Static(s) => { + // replace ${const.xx} + s.replace("$", "${consr.dollar}") + .replace("\\n", "${const.n}") + .replace("\\r", "${const.r}") + .replace("\\t", "${const.t}") + } + PInterpolatedElement::Variable(v) => { + // translate variable name + format!("${{{}}}", v) + } + }) + .collect::>() + .join(""), + delim + ), + Value::Number(_, n) => format!("{}", n), + Value::Boolean(_, b) => format!("{}", b), + Value::EnumExpression(_e) => unimplemented!(), + Value::List(l) => format!( + "[ {} ]", + map_strings_results(l.iter(), |x| self.value_to_string(x, true), ",")? + ), + Value::Struct(s) => format!( + "{{ {} }}", + map_strings_results( + s.iter(), + |(x, y)| Ok(format!(r#""{}":{}"#, x, self.value_to_string(y, true)?)), + "," + )? + ), + }) + } + + fn generate_parameters_metadatas<'src>(&mut self, parameters: Option>) -> String { + let mut params_str = String::new(); + + let mut get_param_field = |param: &Value, entry: &str| -> String { + if let Value::Struct(param) = ¶m { + if let Some(val) = param.get(entry) { + if let Ok(val_s) = self.value_to_string(val, false) { + return match val { + Value::String(_) => format!("{:?}: {:?}", entry, val_s), + _ => format!("{:?}: {}", entry, val_s), + }; + } + } + } + "".to_owned() + }; + + if let Some(Value::List(parameters)) = parameters { + parameters.iter().for_each(|param| { + params_str.push_str(&format!( + "# @parameter {{ {}, {}, {} }}\n", + get_param_field(param, "name"), + get_param_field(param, "id"), + get_param_field(param, "constraints") + )); + }); + }; + params_str + } + + fn generate_ncf_metadata(&mut self, _name: &Token, resource: &ResourceDef) -> Result { + let mut meta = resource.metadata.clone(); + // removes parameters from meta and returns it formatted + let parameters: String = + self.generate_parameters_metadatas(meta.remove(&Token::from("parameters"))); + // description must be the last field + let mut map = map_hashmap_results(meta.iter(), |(n, v)| { + Ok((n.fragment(), self.value_to_string(v, false)?)) + })?; + let mut metadatas = String::new(); + let mut push_metadata = |entry: &str| { + if let Some(val) = map.remove(entry) { + metadatas.push_str(&format!("# @{} {:#?}\n", entry, val)); + } + }; + push_metadata("name"); + push_metadata("description"); + push_metadata("version"); + metadatas.push_str(¶meters); + for (key, val) in map.iter() { + metadatas.push_str(&format!("# @{} {}\n", key, val)); + } + Ok(metadatas) + } + + pub fn format_param_type(&self, value: &Value) -> String { + String::from(match value { + Value::String(_) => "string", + Value::Number(_, _) => "long", + Value::Boolean(_, _) => "bool", + Value::EnumExpression(_) => "enum_expression", + Value::List(_) => "list", + Value::Struct(_) => "struct", + }) + } +} + +impl Generator for DSC { + // TODO methods differ if this is a technique generation or not + fn generate( + &mut self, + gc: &AST, + source_file: Option<&Path>, + dest_file: Option<&Path>, + _generic_methods: &Path, + technique_metadata: bool, + ) -> Result<()> { + let mut files: HashMap = HashMap::new(); + // TODO add global variable definitions + for (rn, res) in gc.resources.iter() { + for (sn, state) in res.states.iter() { + // This condition actually rejects every file that is not the input filename + // therefore preventing from having an output in another directory + // Solutions: check filename rather than path, or accept everything that is not from crate root lib + let file_to_create = match get_dest_file(source_file, sn.file(), dest_file) { + Some(file) => file, + None => continue, + }; + self.reset_context(); + + // get header + let header = match files.get(&file_to_create) { + Some(s) => s.to_string(), + None => { + if technique_metadata { + self.generate_ncf_metadata(rn, res)? // TODO dsc + } else { + String::new() + } + } + }; + + // get parameters + let method_parameters: String = res + .parameters + .iter() + .chain(state.parameters.iter()) + .map(|p| { + format!( + // TODO check if parameter is actually mandatory + " [parameter(Mandatory=$true)]\n [{}]${}", + self.format_param_type(&p.value), + pascebab_case(p.name.fragment()) + ) + }) + .collect::>() + .join(",\n"); + + // add default dsc parameters + let parameters: String = vec![ + String::from(" [parameter(Mandatory=$true)]\n [string]$techniqueName"), + String::from(" [parameter(Mandatory=$true)]\n [string]$reportId"), + String::from(" [switch]$auditOnly"), + method_parameters, + ] + .join(",\n"); + + // get methods + let methods = &state + .statements + .iter() + .map(|st| self.format_statement(gc, st)) + .collect::>>()? + .join("\n"); + // merge header + parameters + methods with technique file body + let content = format!( + r#"# generated by rudder-lang +{header} +function {resource_name}-{state_name} {{ + [CmdletBinding()] + param ( +{parameters} + ) + + $local_classes = New-ClassContext + $resources_dir = $PSScriptRoot + "\resources" + +{methods} +}}"#, + header = header, + resource_name = pascebab_case(rn.fragment()), + state_name = pascebab_case(sn.fragment()), + parameters = parameters, + methods = methods + ); + files.insert(file_to_create, content); + } + } + + // 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::User("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(()) + } +} + +fn pascebab_case(s: &str) -> String { + let chars = s.chars().into_iter(); + + let mut pascebab = String::new(); + let mut is_next_uppercase = true; + for c in chars { + let next = match c { + ' ' | '_' | '-' => { + is_next_uppercase = true; + String::from("-") + } + c => { + if is_next_uppercase { + is_next_uppercase = false; + c.to_uppercase().to_string() + } else { + c.to_string() + } + } + }; + pascebab.push_str(&next); + } + pascebab +} + +fn get_dest_file(input: Option<&Path>, cur_file: &str, output: Option<&Path>) -> Option { + let dest_file = match input { + Some(filepath) => { + if filepath.file_name() != Some(&OsStr::new(cur_file)) { + return None; + } + // can unwrap here since if source_file is Some, so does dest_file (see end of compile.rs) + match output.unwrap().to_str() { + Some(dest_filename) => dest_filename, + None => cur_file, + } + } + None => cur_file, + }; + Some(dest_file.to_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dest_file() { + assert_eq!( + get_dest_file( + Some(Path::new("/path/my_file.rl")), + "my_file.rl", + Some(Path::new("")) + ), + Some("".to_owned()) + ); + assert_eq!( + get_dest_file( + Some(Path::new("/path/my_file.rl")), + "my_file.rl", + Some(Path::new("/output/file.rl.dsc")) + ), + Some("/output/file.rl.dsc".to_owned()) + ); + assert_eq!( + get_dest_file( + Some(Path::new("/path/my_file.rl")), + "wrong_file.rl", + Some(Path::new("/output/file.rl.dsc")) + ), + None + ); + } +} diff --git a/rudder-lang/src/translate.rs b/rudder-lang/src/translate.rs index b0fa70dcb42..902855345a2 100644 --- a/rudder-lang/src/translate.rs +++ b/rudder-lang/src/translate.rs @@ -123,6 +123,7 @@ impl<'src> Translator<'src> { @parameters= [{newline}{parameters_meta}] resource {bundle_name}({parameter_list}) + {bundle_name} state technique() {{ {calls} }} diff --git a/rudder-lang/tests/test_files/tester/parameters/technique.rl.cf b/rudder-lang/tests/test_files/tester/parameters/technique.rl.cf index 44977190f75..db3517e0967 100644 --- a/rudder-lang/tests/test_files/tester/parameters/technique.rl.cf +++ b/rudder-lang/tests/test_files/tester/parameters/technique.rl.cf @@ -2,7 +2,7 @@ # @name parameters # @description technique using parameters # @version 1.0 -# @parameter { "name": "paramtest", "id": "d74a03dd-5b0b-4b06-8dcf-b4e0cb387c60", "constraints": { "max_length":16384,"allow_empty_string":1,"allow_whitespace_string":1 } } +# @parameter { "name": "paramtest", "id": "d74a03dd-5b0b-4b06-8dcf-b4e0cb387c60", "constraints": { "allow_whitespace_string":1,"max_length":16384,"allow_empty_string":1 } } bundle agent parameters_technique(paramtest) { diff --git a/rudder-lang/tools/generate_lib b/rudder-lang/tools/generate_lib index 1ed82ea7305..b144b6ae52b 100644 --- a/rudder-lang/tools/generate_lib +++ b/rudder-lang/tools/generate_lib @@ -36,8 +36,9 @@ foreach my $file (glob $ncf_methods) { my $i=0; my ($resource, $state, $prefix, $class_parameter) = ("", "", "", -1); my $gm = ""; + my @params; + my @resource_params_unnamed=(); my @resource_params=(); - my @state_params=(); my %param_id=(); # loop over GM @@ -46,10 +47,9 @@ foreach my $file (glob $ncf_methods) { if ($line =~ /^#.*\@parameter\s+(\w+)\s.*$/) { $param_id{$1}=$i; if ($i < $resource_identifiers) { - push @resource_params, "p$i"; - } else { - push @state_params, "p$i"; + push @resource_params_unnamed, "p$i"; } + push @params, "$1"; $i++; } elsif($line =~ /^#.*\@class_prefix\s+(\w+)/) { @@ -57,6 +57,8 @@ foreach my $file (glob $ncf_methods) { } elsif($line =~ /^#.*\@class_parameter\s+(\w+)/) { $class_parameter = $param_id{$1}; + splice @params, $class_parameter, 1; + push @resource_params, "\"$1\": $class_parameter"; } elsif($line =~ /^bundle agent (\w+)\(/) { $gm = $1; @@ -76,8 +78,8 @@ foreach my $file (glob $ncf_methods) { } # keep output - $resource{"resource $resource(".join(",",@resource_params).")"}=1; - push @state, "$resource state $state(".join(",",@state_params)."){}"; + $resource{"resource $resource(".join(", ",@resource_params_unnamed).")"}=1; + push @state, "\@class_parameters={".join(", ", @resource_params)."}\n$resource state $state(".join(", ",@params).") {}\n"; push @config_lines, "$gm = { class_prefix=\"$prefix\", class_parameter_id = $class_parameter }"; } diff --git a/rudder-lang/tools/generic_methods.toml b/rudder-lang/tools/generic_methods.toml index fcce6567c00..4850c53d7f7 100644 --- a/rudder-lang/tools/generic_methods.toml +++ b/rudder-lang/tools/generic_methods.toml @@ -22,6 +22,8 @@ directory_create = { class_prefix="directory_create", class_parameter_id = 0 } directory_present = { class_prefix="directory_present", class_parameter_id = 0 } environment_variable_present = { class_prefix="environment_variable_present", class_parameter_id = 0 } file_absent = { class_prefix="file_absent", class_parameter_id = 0 } +file_augeas_commands = { class_prefix="file_augeas_commands", class_parameter_id = 1 } +file_augeas_set = { class_prefix="file_augeas_set", class_parameter_id = 2 } file_block_present = { class_prefix="file_block_present", class_parameter_id = 0 } file_block_present_in_section = { class_prefix="file_block_present_in_section", class_parameter_id = 0 } file_check_block_device = { class_prefix="file_check_block_device", class_parameter_id = 0 } @@ -150,9 +152,11 @@ service_stop = { class_prefix="service_stop", class_parameter_id = 0 } service_stopped = { class_prefix="service_stopped", class_parameter_id = 0 } sharedfile_from_node = { class_prefix="sharedfile_from_node", class_parameter_id = 1 } sharedfile_to_node = { class_prefix="sharedfile_to_node", class_parameter_id = 1 } +sysctl_value = { class_prefix="sysctl_value", class_parameter_id = 0 } user_absent = { class_prefix="user_absent", class_parameter_id = 0 } user_create = { class_prefix="user_create", class_parameter_id = 0 } user_fullname = { class_prefix="user_fullname", class_parameter_id = 0 } +user_group = { class_prefix="user_group", class_parameter_id = 0 } user_home = { class_prefix="user_home", class_parameter_id = 0 } user_locked = { class_prefix="user_locked", class_parameter_id = 0 } user_password_hash = { class_prefix="user_password_hash", class_parameter_id = 0 } @@ -170,6 +174,7 @@ variable_iterator = { class_prefix="variable_iterator", class_parameter_id = 1 } variable_iterator_from_file = { class_prefix="variable_iterator_from_file", class_parameter_id = 1 } variable_string = { class_prefix="variable_string", class_parameter_id = 1 } variable_string_default = { class_prefix="variable_string_default", class_parameter_id = 1 } +variable_string_from_augeas = { class_prefix="variable_string_from_augeas", class_parameter_id = 1 } variable_string_from_command = { class_prefix="variable_string_from_command", class_parameter_id = 1 } variable_string_from_file = { class_prefix="variable_string_from_file", class_parameter_id = 1 } variable_string_from_math_expression = { class_prefix="variable_string_from_math_expression", class_parameter_id = 1 }