Fix 'authorization' property one more time

Due to the fact how dispatching works the 'authorization' property
handler was not always invoked through Properties.gen_xml(), leading to
a bug and an invalid test case: project-with-auth-properties.(yaml/xml)

This change pushes the logic for determining if the object is a
folder/multi-branch project from Properties.gen_xml() to the
authorization() function itself. For that to work the authorization()
function needed access to the top-level job object, which is now
conditionally passed to each dispatched function as a keyword argument,
if the function takes 'job_data' argument. Note that taking this
argument is completely optional so no changes were required in other
handlers. In the future the same approach could be taken to eliminate
the hacks for 'uno-choice' in Parameters.gen_xml().

Additionally ModuleRegistry.dispatch() now merges the top-level job
object with any template data before deep-formatting, so that job-level
properties are now available in Jinja templates. A very nice use case
is in project-with-auth-j2-yaml.yaml test case.

Change-Id: I9a49de74055cd9acfdc87dbad1fc454548643e8f
This commit is contained in:
Adam Romanek 2021-02-02 11:56:21 +01:00
parent 575b0520f1
commit c0866c56b9
5 changed files with 74 additions and 21 deletions

View File

@ -527,7 +527,7 @@ def authenticated_build(registry, xml_parent, data):
).text = "hudson.model.Item.Build:authenticated"
def authorization(registry, xml_parent, data):
def authorization(registry, xml_parent, data, job_data):
"""yaml: authorization
Specifies an authorization matrix
@ -564,8 +564,7 @@ def authorization(registry, xml_parent, data):
:language: yaml
"""
# check if it's a folder or a job
is_a_folder = data.pop("_is_a_folder", None) if data else False
is_a_folder = job_data.get("project-type") in ("folder", "multibranch")
credentials = "com.cloudbees.plugins.credentials.CredentialsProvider."
ownership = "com.synopsys.arc.jenkins.plugins.ownership.OwnershipPlugin."
@ -1434,15 +1433,4 @@ class Properties(jenkins_jobs.modules.base.Base):
properties = XML.SubElement(xml_parent, "properties")
for prop in data.get("properties", []):
# Pass a flag for folder permissions to the authorization method
if next(iter(prop)) == "authorization":
# Only projects are placed in folders
if "project-type" in data:
if data["project-type"] in ("folder", "multibranch"):
prop["authorization"]["_is_a_folder"] = True
else:
prop["authorization"]["_is_a_folder"] = False
else:
prop["authorization"]["_is_a_folder"] = False
self.registry.dispatch("property", properties, prop)
self.registry.dispatch("property", properties, prop, job_data=data)

View File

@ -15,12 +15,15 @@
# Manage Jenkins plugin module registry.
import inspect
import logging
import operator
import pkg_resources
import re
import types
from six import PY2
from jenkins_jobs.errors import JenkinsJobsException
from jenkins_jobs.formatter import deep_format
from jenkins_jobs.local_yaml import Jinja2Loader
@ -29,6 +32,8 @@ __all__ = ["ModuleRegistry"]
logger = logging.getLogger(__name__)
getargspec = inspect.getargspec if PY2 else inspect.getfullargspec
class ModuleRegistry(object):
_entry_points_cache = {}
@ -87,6 +92,14 @@ class ModuleRegistry(object):
return plugins_info_dict
@staticmethod
def _filter_kwargs(func, **kwargs):
arg_spec = getargspec(func)
for name in list(kwargs.keys()):
if name not in arg_spec.args:
del kwargs[name]
return kwargs
def get_plugin_info(self, plugin_name):
"""Provide information about plugins within a module's impl of Base.gen_xml.
@ -141,7 +154,9 @@ class ModuleRegistry(object):
return component_list_type
def dispatch(self, component_type, xml_parent, component, template_data={}):
def dispatch(
self, component_type, xml_parent, component, template_data={}, job_data=None
):
"""This is a method that you can call from your implementation of
Base.gen_xml or component. It allows modules to define a type
of component, and benefit from extensibility via Python
@ -151,8 +166,10 @@ class ModuleRegistry(object):
(e.g., `builder`)
:arg YAMLParser parser: the global YAML Parser
:arg Element xml_parent: the parent XML element
:arg component: component definition
:arg dict template_data: values that should be interpolated into
the component definition
:arg dict job_data: full job definition
See :py:class:`jenkins_jobs.modules.base.Base` for how to register
components of a module.
@ -173,13 +190,16 @@ class ModuleRegistry(object):
# The component is a singleton dictionary of name: dict(args)
name, component_data = next(iter(component.items()))
if template_data or isinstance(component_data, Jinja2Loader):
paramdict = {}
paramdict.update(template_data)
paramdict.update(job_data or {})
# Template data contains values that should be interpolated
# into the component definition. To handle Jinja2 templates
# that don't contain any variables, we also deep format those.
try:
component_data = deep_format(
component_data,
template_data,
paramdict,
self.jjb_config.yamlparser["allow_empty_variables"],
)
except Exception:
@ -280,10 +300,13 @@ class ModuleRegistry(object):
# Pass component_data in as template data to this function
# so that if the macro is invoked with arguments,
# the arguments are interpolated into the real defn.
self.dispatch(component_type, xml_parent, b, component_data)
self.dispatch(
component_type, xml_parent, b, component_data, job_data=job_data
)
elif name in eps:
func = eps[name]
func(self, xml_parent, component_data)
kwargs = self._filter_kwargs(func, job_data=job_data)
func(self, xml_parent, component_data, **kwargs)
else:
raise JenkinsJobsException(
"Unknown entry point or macro '{0}' "

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<project>
<actions/>
<description>&lt;!-- Managed by Jenkins Job Builder --&gt;</description>
<keepDependencies>false</keepDependencies>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<concurrentBuild>false</concurrentBuild>
<canRoam>true</canRoam>
<properties>
<hudson.security.AuthorizationMatrixProperty>
<inheritanceStrategy class="org.jenkinsci.plugins.matrixauth.inheritance.InheritParentStrategy"/>
<permission>hudson.model.Item.Build:john</permission>
<permission>hudson.model.Item.Cancel:john</permission>
<permission>hudson.model.Item.Read:john</permission>
<permission>hudson.model.Item.Build:megan</permission>
<permission>hudson.model.Item.Cancel:megan</permission>
<permission>hudson.model.Item.Read:megan</permission>
<permission>hudson.model.Item.Build:steve</permission>
<permission>hudson.model.Item.Cancel:steve</permission>
<permission>hudson.model.Item.Read:steve</permission>
</hudson.security.AuthorizationMatrixProperty>
</properties>
<scm class="hudson.scm.NullSCM"/>
<builders/>
<publishers/>
<buildWrappers/>
</project>

View File

@ -0,0 +1,14 @@
- job:
name: test
authorized_people:
- john
- megan
- steve
properties:
- authorization: !j2-yaml: |
{% for user in authorized_people %}
{{ user }}:
- job-build
- job-cancel
- job-read
{% endfor %}

View File

@ -13,10 +13,10 @@
<concurrentBuild>false</concurrentBuild>
<canRoam>true</canRoam>
<properties>
<hudson.security.AuthorizationMatrixProperty>
<com.cloudbees.hudson.plugins.folder.properties.AuthorizationMatrixProperty>
<inheritanceStrategy class="org.jenkinsci.plugins.matrixauth.inheritance.InheritParentStrategy"/>
<permission>hudson.model.Item.Build:auser</permission>
</hudson.security.AuthorizationMatrixProperty>
</com.cloudbees.hudson.plugins.folder.properties.AuthorizationMatrixProperty>
</properties>
<scm class="hudson.scm.NullSCM"/>
<publishers/>