Skip to content

Actions

This section documents each action type that Simple-CDD-YAML is able to use in YAML recipes.

Usage

Actions are listed under the root level actions keyword, which usually appears after the variables and profile name definition:

{% set profile=profile or "base" -%} # (1)!

profile: {{profile}} # (2)!

actions:
  action: recipe
    description: ...
  1. Note the value profile or "base". This assigns the default value base to the variable profile. When defining a variable, it's recommended to set a default value.
  2. Here, the value of the variable profile is substituted as profile name.

Implementation

All actions inherit from the Action class.

Action

Abstract action base class

Source code in simple_cdd_yaml/actions.py
class Action:
    """ Abstract action base class """
    action_out = None

    def __init__(self, args_dict):
        self.profile = args_dict['profile']
        self.input_dir = pl.Path(args_dict['input'])
        self.output_dir = pl.Path(args_dict['output'])
        self.debos = args_dict['debos']
        self.debos_output_dir = pl.Path(args_dict['debos_output']) / self.profile
        self.result = {
            'architecture': '',
            'chroot_default': False,
            'actions': [], 
            'pre-actions': [], 
            'post-actions': [],
        }

    @staticmethod
    def _print(text, header=None, width=68):
        """ Print text wrapped """
        initial_indent = ' '
        if header:
            print(f' {header}')
            initial_indent = '  '
        wrapped_text = textwrap.wrap(
            text,
            width=width,
            initial_indent=initial_indent,
            subsequent_indent='  ',
            break_on_hyphens=False,
        )
        print('\n'.join(wrapped_text))

    def _action_inform(self, props):
        """ Inform on action start action status """
        if action_type := props.get('action'):
            print(f' {action_type} action '.upper().center(70, '='))
        if description := props.get('description'):
            self._print(f'Description: {description}')
        for src_file in ('recipe', 'preconf', 'source', 'command', 'script'):
            if src := props.get(src_file):
                self._print(f'{src_file.capitalize()}: {src}')

    def _read_substitute(self, filename, substitutions):
        """ Read string from file and perform jinja2 substitutions """
        input_file = self.input_dir / filename
        with open(input_file, mode='r', encoding='utf-8') as file:
            template = jinja2.Template(file.read())
        return template.render(substitutions)

    def _write_action(self, string, extension, directory='profiles',
                      no_duplicate=False):
        """ Append string to profile file """
        filename = self.profile + '.' + extension
        output_file = self.output_dir / directory / filename
        if no_duplicate and output_file.is_file():
            with open(output_file, mode='r', encoding='utf-8') as file:
                if string in file.read():
                    return
        with open(output_file, mode='a', encoding='utf-8') as file:
            file.write(string)

    def perform_action(self, props):
        """ Perform the action specific tasks and return result """
        raise NotImplementedError('Action is an abstract base class!')

    def perform_debos_action(self, props):
        """ Process debos action specific tasks and return result """
        raise NotImplementedError('Action is an abstract base class!')

    def execute(self, props):
        """ Execute an action """
        self._action_inform(props)
        if self.debos:
            result = self.perform_debos_action(props)
        else:
            result = self.perform_action(props)
        if result:
            if not self.action_out:
                self.action_out = props['action']
            self._write_action(result, self.action_out)

    def append_result(self, new_result: dict, key='actions'):
        """ Append a new result to the result list """
        added_result = copy.deepcopy(new_result)
        self.result[key].append(added_result)

    def extend_result(self, new_result: dict, key='actions'):
        """ Append a new result to the result list """
        added_result = copy.deepcopy(new_result)
        self.result[key].extend(added_result)

    def prepend_result(self, new_result: dict, key='actions'):
        """ Prepend a new result to the result list """
        added_result = copy.deepcopy(new_result)
        self.result[key].insert(0, added_result)

    def combine_results(self, result):
        """ Combine two result sets """
        for key in ('actions', 'pre-actions', 'post-actions'):
            self.result[key].extend(result[key])
        for option in ('architecture', 'chroot_default'):
            if result.get(option):
                self.result[option] = result[option]

    def unique_filename(self, base='script', ext='sh', description=None):
        """ Create a name from description or using uuid """
        name = base + '_'
        if description:
            name += description.lower()
        else:
            name += str(uuid.uuid4().hex)
        return "".join([x if x.isalnum() else "_" for x in name]) + '.' + ext