Skip to content

Commit

Permalink
Add support for external variables
Browse files Browse the repository at this point in the history
External variables can be defined either on kernel command
line or via Beaker task parameters. Documentation for the new
functionality is included.
  • Loading branch information
jikortus authored and jstodola committed Sep 4, 2023
1 parent 8caac0f commit 6708c8e
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 10 deletions.
22 changes: 22 additions & 0 deletions anabot/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from logging.handlers import SysLogHandler
from anabot import config
from anabot.variables import set_variable, get_variable, set_env_variable
import shlex

CMDLINE_VAR_PREFIX = "anabot."

def show_help(arg0):
print('%s profile_name [recipe_url] [varname=value[,varname=value]]' % sys.argv[0])
Expand Down Expand Up @@ -107,6 +110,25 @@ def main(*args):
logger.debug('Running preexec hooks')
run_preexechooks()

# import variables from kernel command line after removing CMDLINE_VAR_PREFIX
with open("/proc/cmdline", "r") as c:
cmdline = c.read()
cmdline_params = shlex.split(cmdline)
var_params = [p for p in cmdline_params if p.startswith(CMDLINE_VAR_PREFIX)]
for param in var_params:
var_pair = param.split("=")
var_name = var_pair[0][len(CMDLINE_VAR_PREFIX):] # get rid of prefix
var_value = var_pair[1]
if get_variable(var_name, None):
var_message = "Redefining internal variable '%s' from command line "\
"to '%s' (originally set to '%s')!" % \
(var_name, var_value, get_variable(var_name))
else:
var_message = "Setting internal variable from command line: %s = '%s'" % \
(var_name, var_value)
set_variable(var_name, var_value)
logger.info(var_message)

# check recipe
if not os.path.exists("/var/run/anabot/raw-recipe.xml"):
reporter.log_error("No anabot recipe found!")
Expand Down
5 changes: 4 additions & 1 deletion anabot/preprocessor/sub.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ def sub_element(element):
element.setName(name)
element.unsetNsProp(element.ns(), "name")
for attribute in element.xpathEval("@*"):
attribute.setContent(attribute.content.format(**get_variables()))
try:
attribute.setContent(attribute.content.format(**get_variables()))
except KeyError as e:
raise Exception("Undefined variable for substitution in Anabot recipe: %s" % e.args[0])
75 changes: 75 additions & 0 deletions doc/external_variables.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
==================
External variables
==================

It is possible to pass external variables to Anabot, using either
kernel command line or Beaker task parameters. The main use
of externally defined variables is for enhancing recipes, where
a variable placeholder can be substituted by Anabot's preprocessor,
thus providing a greater flexibility in comparison with just static
recipes.

.. note::
Even though the main reason for introducing external variables
is their use in recipes, it is technically possible to use
those variables generally in Anabot's code, since those variables
are internally handled by |get_variable|_ and |set_variable|_.

.. |get_variable| replace:: ``get_variable()``
.. _get_variable: https://github.com/rhinstaller/anabot/blob/main/anabot/variables.py
.. |set_variable| replace:: ``set_variable()``
.. _set_variable: https://github.com/rhinstaller/anabot/blob/main/anabot/variables.py

.. warning::
When defining external variables, chooose their names wisely.
As mentioned in the note above, they occupy the shared internal
variables space, which will lead to a clash when another variable with
the same name is used elsewhere in Anabot's code, leading to
undefined behaviour.

A list of already existing variables in Anabot code using this
mechanism can be obtained for instance by running the following command:
in Anabot repository:
``git grep -Pho "(get|set)_variable\([\"']\K[^'\"]+" | sort -u``

.. warning::
If a variable is defined both on kernel command line and in Beaker
task parameter, the command line one takes precedence. However, it is best
to avoid defining the same variable using both mechanisms at the same time.

Kernel command line
===================
It is possible to define an external variable on kernel command line using
``anabot.variable_name=variable_value`` or ``anabot.variable_name="variable value"``.
The ``anabot.`` prefix will be removed and a variable named ``variable_name``
with value ``variable_value`` (or ``variable value`` respectively) will be defined.

Beaker task parameters
======================
Another option to pass the variables is to use Beaker task parameters.
The parameters use ``ANABOT_SUB_`` prefix, which is removed and the rest
of variable name is converted to lowercase.

For example, ``<param name="ANABOT_SUB_LANG" value="Czech">`` will translate
into a variable named ``lang`` with value ``Czech``.

Use in recipes
==============
Variable placeholders in recipes are substituted by external variables passed
to Anabot by means of Python string template formatting (``str.format()``)
in conjunction with a dedicated ``<ez:sub/>`` element. This element handles
a substitution for an element specified by its ``ez:name`` attribute.

.. note::
It's necessary to use the ``sub`` element with the ``ez`` namespace specified,
otherwise it won't be recognized and Anabot will end up with an error.
The same notice is valid for the ``name`` attribute.

It is strongly advised to use only **lowercase** placeholder names, since variable
names originating from Beaker parameters always get converted to lowercase.

For example, ``<ez:sub ez:name="language" value="{lang}" />`` in the recipe with the
``lang`` variable set to ``Czech`` (defined by ``anabot.lang`` on kernel command
line or ``ANABOT_SUB_LANG`` Beaker task parameter) will get substituted for
``<language value="Czech" />``.

1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Contents

101
workflow
external_variables
decorators
history
debugging
Expand Down
9 changes: 0 additions & 9 deletions examples/sub.xml

This file was deleted.

24 changes: 24 additions & 0 deletions examples/variable_substitution.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<ez:installation xmlns:ez="http://fedoraproject.org/anaconda/anabot/recipe/tiny/1">
<!--
When running preprocessor manually:
./preprocessor.py examples/sub.xml - profile_name lang=English full_name="Justin Time" ...
When using external variables, those have to be defined either
on kernel command line (anabot.lang=English anabot.full_name="Justin Time" ...)
or in Beaker task parameter with 'ANABOT_SUB_' prefix
(<param name="ANABOT_SUB_LANG" value='English' />
<param name="ANABOT_SUB_FULL_NAME" value='Justin Time' /> ...).
-->
<ez:welcome>
<ez:sub ez:name="language" value="{lang}" />
</ez:welcome>
<ez:hub>
<create_user>
<ez:sub ez:name="full_name" value="{full_name}" />
<ez:sub ez:name="username" value="{username}" />
<ez:sub ez:name="password" value="{password}" />
<ez:sub ez:name="confirm_password" value="{password}" />
<done />
</create_user>
</ez:hub>
</ez:installation>
21 changes: 21 additions & 0 deletions modules/beaker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from anabot.variables import set_variable, get_variable

BEAKER = "https://%s" % get_variable('beaker_hub_hostname')
EXTERNAL_VAR_PREFIX = "ANABOT_SUB_"
with open('/proc/cmdline', 'r') as proc_cmdline:
cmdline = proc_cmdline.read()

Expand Down Expand Up @@ -204,3 +205,23 @@ def param_value(name, default=None, empty_default=True):
set_variable("beta", "1")
else:
rep.log_debug("BETA task param not found, empty or zero in beaker recipe")

# get all parameter names for the current Beaker task
def get_param_names():
param_xpath = '/job/recipeSet/recipe/task[@id="%s"]/params/param/@name' % task_id
param_names = [name.content for name in xml.xpathEval(param_xpath)]
if len(param_names) > 0:
rep.log_debug("Current Beaker task parameters: %s" % param_names)
else:
rep.log_debug("No parameters for current Beaker task '%s' found!" % task_id)
return param_names

# use all task parameters beginning with EXTERNAL_VAR_PREFIX as Anabot variables,
# the variable name will be parameter name stripped from prefix and converted to lowercase
for param_name in [n for n in get_param_names() if n.startswith(EXTERNAL_VAR_PREFIX)]:
var_name = param_name[len(EXTERNAL_VAR_PREFIX):].lower()
var_value = param_value(param_name)
rep.log_debug("Setting internal variable based on task parameter: %s = '%s'" %
(var_name, var_value))
set_variable(var_name, var_value)

0 comments on commit 6708c8e

Please sign in to comment.