Add new methods to the technique editor
Use case
When some specifics use cases are not supported by Rudder, you may want to be able to create your own methods usable in the technique editor.
This document will go through the complete writing of a method that does not exist in the current Rudder library.
Planning
For this example we will want to create a method that let us define a Rudder variable based on the result of a template.
To do this we will use the pre-existing Mustache templating methods furnished in Rudder: * Mustache: it is Rudder’s native templating engine, with a very simple syntax, and the best performance. It should be the default choice for most cases.
Prerequisites
All our Linux methods are built on the same manner, using the underlying tool CFEngine. Some basic knowledge on CFEngine will be required to fully understand this guide. In Rudder we are using a slightly different vocabulary than CFEngine:
-
The
classes
are calledconditions
in Rudder -
The
bundle name
is themethod name
Create a technique editor understandable method
First we need to define a minimal bundle that will become our future new method.
All methods are always bundles of the agent type
.
The bundle name must respects the [a-z_]+
syntax.
The bundle will take 3 parameters:
* variable_prefix
and variable_name
to describe the full naming space of the variable
we want to set.
* template_path
which is the path of the source template we will use.
Let’s create the file variable_string_from_mustache_file.cf
in /var/rudder/configuration-repository/ncf/30_generic_methods/
containing:
bundle agent variable_string_from_mustache_file(variable_prefix, variable_name, template_path)
{
}
Method metadata
Then, let’s add some Rudder’s specific tags to the parameters, which are used by the technique editor to interpret the method.
# @name Variable string from mustache template
# @description Define a variable from an expanded mustache template
#
# @parameter variable_prefix The prefix of the variable name
# @parameter variable_name The variable to define, the full name will be variable_prefix.variable_name
# @parameter template_path The path of the mustache template file
#
# @class_prefix variable_string_from_mustache_template
# @class_parameter variable_name
bundle agent variable_string_from_mustache_file(variable_prefix, variable_name, template_path)
{
}
-
@name
is obviously the method name, as it will be displayed in the technique editor -
@description
is a small description of the goal of the method -
@documentation
can be multiline and support the Markdown syntax -
@parameter
is used to define a small description of the linked parameter-
The syntax is
@parameter <parameter name> <description>
-
-
@class_prefix
andclass_parameter
will define the common base of the resulting classes generated by the method.-
The resulting classes will be variable_string_from_mustache_template_<class parameter>_<result status>
-
With:
-
<class parameter>
being the value passed to the parameter set as the class_parameter -
<result status>
is eithersuccess|repaired|error
-
-
We now have a minimal method, which is understandable by the technique editor, but which can not report nor do anything.
Test it
To test it in the interface, we now need to commit the file in the Rudder configuration-repository and reload the Rudder techniques library.
cd /var/rudder/configuration-repository/ncf/30_generic_methods
git add variable_string_from_mustache_file.cf
git commit -m "Adding v0 of variable_string_from_mustache_file"
rudder server reload-techniques
Whenever you create new methods, to avoid upgrade conflict/outage, always place them in the /var/rudder/configuration-repository/ncf/30_generic_methods
.
And whenever you will need to modify one of those, commit the changes and reload the technique library.
You should now be able to see your new method in the technique editor.
Method core
Old class prefix
In Rudder we are always using the same base code to define the resulting classes with a variable named old_class_prefix
, used to define the resulting classes
used in the technique editor.
# Basically the building of our result class, without the status
"old_class_prefix" string => canonify("<@class_prefix>_<@class_parameter>");
# In our example:
"old_class_prefix" string => canonify("variable_string_from_mustache_template_${variable_name}");
Class prefix
Another variable class_prefix
, used to differentiate the different calls to the same method.
It basically is the old_class_prefix but build with the concatenation of all the args of the methods.
# args it a list of all the args
"args" slist => { "${variable_prefix}", "${variable_name}", "${template_path}" };
# we concatenate them
"report_param" string => join("_", args);
# make a complete class_prefix out of it
"full_class_prefix" string => canonify("variable_string_from_mustache_template_${report_param}");
#trunc it since super long conditions may cause some extra issues
"class_prefix" string => string_head("${full_class_prefix}", "1000");
Reporting
Finally, the reporting is done by another module called _log_v3
which takes as parameters:
# Message is the common part that will be displayed in the Rudder report, it will be suffixed # with the result status. bundle agent _log_v3(message, class_parameter, old_class_prefix, class_prefix, args)
Since it is another bundle, we need to call it using a usebundle
promise in methods
sections.
Summary
At this point, we should have a method looking like this:
# Not showing the comments parts anymore bundle agent variable_string_from_mustache_file(variable_prefix, variable_name, template_path) { vars: "old_class_prefix" string => canonify("variable_string_from_mustache_template_${variable_name}"); "args" slist => { "${variable_prefix}", "${variable_name}", "${template_path}" }; "report_param" string => join("_", args); "full_class_prefix" string => canonify("variable_string_from_mustache_template_${report_param}"); "class_prefix" string => string_head("${full_class_prefix}", "1000"); methods: "report" usebundle => _log_v3("Set the string ${variable_prefix}.${variable_name} to the mustache template expansion of ${template_path}", "${variable_name}", "${old_class_prefix}", "${class_prefix}", @{args}); }
Make our method do something
We can now start adding state changes in out code. The goal was to define a method to set a variable from the result of a Mustache template. To do that we need to:
-
Load the template file
-
Apply it
-
Create a variable from its result
-
Do some reporting
# Not showing the comments parts anymore bundle agent variable_string_from_mustache_file(variable_prefix, variable_name, template_path) { vars: "old_class_prefix" string => canonify("variable_string_from_mustache_template_${variable_name}"); "args" slist => { "${variable_prefix}", "${variable_name}", "${template_path}" }; "report_param" string => join("_", args); "full_class_prefix" string => canonify("variable_string_from_mustache_template_${report_param}"); "class_prefix" string => string_head("${full_class_prefix}", "1000"); # define the variable within the variable_prefix namespace "template" string => readfile("${template_path}" , "0"); (1) "content" string => string_mustache("${template}" , datastate()); (2) "${variable_prefix}.${variable_name}" string => "${content}"; (3) classes: "variable_defined" expression => isvariable("${variable_prefix}.${variable_name}"); (4) !variable_defined:: (5) "error" usebundle => _classes_failure("${old_class_prefix}"); "error" usebundle => _classes_failure("${class_prefix}"); variable_defined:: "success" usebundle => _classes_success("${old_class_prefix}"); "success" usebundle => _classes_success("${class_prefix}"); methods: "report" usebundle => _log_v3("Set the string ${variable_prefix}.${variable_name} to the mustache template expansion of ${template_path}", "${variable_name}", "${old_class_prefix}", "${class_prefix}", @{args}); }
1 | Load the content of the template in template |
2 | Apply the template in the variable content |
3 | Assign its content to the goal variable <variable_prefix.variable_name> |
4 | Test that the goal variable is well defined, if yes, define the local condition variable_define
<5>
|
You can now commit and try to use the method.
To go further
Now that we have a functional method, we could try to make it more bullet proof. * What if the template file does not exists * Or the template is malformed?
You can try to avoid this cases by checking the datastate before reporting, or by
assigning a default value to our content
variable before template expansion and
compare it afterward.
← Use relays and server as repository mirrors Use secrets in configuration policies →