Source code for jenni.models.groovyjobbase

import logging
import os
import re
from typing import List

from jenni.models import PipelineJobBase
from jenni.utils import *


[docs]class GroovyJobBase(PipelineJobBase): """ Baseclass for jobs that are implemented using Groovy pipeline scripts (https://www.jenkins.io/doc/book/pipeline/#scripted-pipeline-fundamentals). """ _CODE_REPLACEMENT_REGEX = re.compile(r"""\{python\((?P<quotes>['"]{1,3})(.*?)(?P=quotes)\)\}""")
[docs] def __init__(self, **kwargs): """ :param kwargs: optional arguments. Passed through to :class:`~jenni.models.PipelineJobBase` :func:`~jenni.models.PipelineJobBase.__init__`. """ super().__init__(**kwargs) #: a list of tuples, each either: #: #: * ``("include", "filename")` #: * ``("text", "groovy code...")`` self._script_specs = []
[docs] def include(self, filename: str): """ Include the specified file (normally Groovy scripted pipeline code) into the job. Contents is checked for occurances of ``{python('''<code>''')}`` strings, and replaced with the result of executing ``<code>``. Instead of 3 single quotes, 3 double, or 1 single/double quote can be used. :param filename: See :func:`~jenni.models.itembase.ItemBase.lookup_relative_filename` for how a file may be found. """ if not os.path.exists(self.lookup_relative_filename(filename)): raise FileNotFoundError(f"Cannot find included file ({filename})") self._script_specs.append(("include", filename))
[docs] def include_first(self, filenames: List[str]): """ Include the first found amongst the specified files (normally Groovy scripted pipeline code) into the job. See :func:`include` for ``{python('''<code>''')}`` replacements. :param filenames: See :func:`~jenni.models.itembase.ItemBase.lookup_relative_filename` for how each file may be found. """ for filename in filenames: if os.path.exists(self.lookup_relative_filename(filename)): self.include(filename) return raise Exception(f"Cannot find any of the list of files to include the first one found of: ({filenames})")
[docs] def code(self, code: str): """ Include the specified text (normally Groovy scripted pipeline code) into the job. See :func:`include` for ``{python('''<code>''')}`` replacements. :param code: literal text to be inserted into the job pipeline script. """ self._script_specs.append(("text", code))
[docs] def get_file_contents(self, filename: str) -> str: """ Return the contents of the file. No replacements are done like in the :func:`include` method. :param filename: See :func:`~jenni.models.itembase.ItemBase.lookup_relative_filename` for how the file may be found. """ with open(self.lookup_relative_filename(filename)) as fp: return fp.read()
def _get_script(self) -> str: snippets = [] for spec in self._script_specs: if spec[0] == "text": self._append_code("text", tidy_text(spec[1]), snippets) elif spec[0] == "include": snippets.append(f"\n// scriptFile: {spec[1]}") self._append_code(spec[1], self.get_file_contents(spec[1]), snippets) else: raise Exception(f"Unknown script spec (bug?): {spec}") return "\n".join(snippets).strip() def _append_code(self, source, code, snippets): def replacer(matchobj): py_code = matchobj.group(2) # logging.debug(f"Replacing {py_code} by ...") # Adding nosec tag to suppress bandit raising # >> Issue: [B307:blacklist] Use of possibly insecure function - consider using safer ast.literal_eval. # We deem this safe because the input being processed are files under our control. result = eval(py_code, globals(), dict(self=self)) # nosec # logging.debug(f"... by {result}") return result try: code = self._CODE_REPLACEMENT_REGEX.sub(replacer, code) snippets.append(code) except Exception as ex: logging.error(f"{source}: {ex}") raise ex