1# Copyright 2015 Thanh Ha
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from functools import wraps
16import logging
17import sys
18
19import xml.etree.ElementTree as XML
20
21from jenkins_jobs.errors import InvalidAttributeError
22from jenkins_jobs.errors import JenkinsJobsException
23from jenkins_jobs.errors import MissingAttributeError
24from jenkins_jobs.modules import hudson_model
25
26import pkg_resources
27
28
29def build_trends_publisher(plugin_name, xml_element, data):
30    """Helper to create various trend publishers."""
31
32    def append_thresholds(element, data, only_totals):
33        """Appends the status thresholds."""
34
35        for status in ["unstable", "failed"]:
36            status_data = data.get(status, {})
37
38            limits = [
39                ("total-all", "TotalAll"),
40                ("total-high", "TotalHigh"),
41                ("total-normal", "TotalNormal"),
42                ("total-low", "TotalLow"),
43            ]
44
45            if only_totals is False:
46                limits.extend(
47                    [
48                        ("new-all", "NewAll"),
49                        ("new-high", "NewHigh"),
50                        ("new-normal", "NewNormal"),
51                        ("new-low", "NewLow"),
52                    ]
53                )
54
55            for key, tag_suffix in limits:
56                tag_name = status + tag_suffix
57                XML.SubElement(element, tag_name).text = str(status_data.get(key, ""))
58
59    # Tuples containing: setting name, tag name, default value
60    settings = [
61        ("healthy", "healthy", ""),
62        ("unhealthy", "unHealthy", ""),
63        ("health-threshold", "thresholdLimit", "low"),
64        ("plugin-name", "pluginName", plugin_name),
65        ("default-encoding", "defaultEncoding", ""),
66        ("can-run-on-failed", "canRunOnFailed", False),
67        ("use-stable-build-as-reference", "useStableBuildAsReference", False),
68        ("use-previous-build-as-reference", "usePreviousBuildAsReference", False),
69        ("use-delta-values", "useDeltaValues", False),
70        ("thresholds", "thresholds", {}),
71        ("should-detect-modules", "shouldDetectModules", False),
72        ("dont-compute-new", "dontComputeNew", True),
73        ("do-not-resolve-relative-paths", "doNotResolveRelativePaths", False),
74        ("pattern", "pattern", ""),
75    ]
76
77    thresholds = ["low", "normal", "high"]
78
79    for key, tag_name, default in settings:
80        xml_config = XML.SubElement(xml_element, tag_name)
81        config_value = data.get(key, default)
82
83        if key == "thresholds":
84            append_thresholds(
85                xml_config, config_value, data.get("dont-compute-new", True)
86            )
87        elif key == "health-threshold" and config_value not in thresholds:
88            raise JenkinsJobsException(
89                "health-threshold must be one of %s" % ", ".join(thresholds)
90            )
91        else:
92            if isinstance(default, bool):
93                xml_config.text = str(config_value).lower()
94            else:
95                xml_config.text = str(config_value)
96
97
98def config_file_provider_builder(xml_parent, data):
99    """Builder / Wrapper helper"""
100    xml_files = XML.SubElement(xml_parent, "managedFiles")
101
102    files = data.get("files", [])
103    for file in files:
104        xml_file = XML.SubElement(
105            xml_files, "org.jenkinsci.plugins." "configfiles.buildwrapper.ManagedFile"
106        )
107        mapping = [
108            ("file-id", "fileId", None),
109            ("target", "targetLocation", ""),
110            ("variable", "variable", ""),
111            ("replace-tokens", "replaceTokens", False),
112        ]
113        convert_mapping_to_xml(xml_file, file, mapping, fail_required=True)
114
115
116def config_file_provider_settings(xml_parent, data):
117    SETTINGS_TYPES = ["file", "cfp"]
118    settings = {
119        "default-settings": "jenkins.mvn.DefaultSettingsProvider",
120        "settings": "jenkins.mvn.FilePathSettingsProvider",
121        "config-file-provider-settings": "org.jenkinsci.plugins.configfiles.maven.job.MvnSettingsProvider",
122        "default-global-settings": "jenkins.mvn.DefaultGlobalSettingsProvider",
123        "global-settings": "jenkins.mvn.FilePathGlobalSettingsProvider",
124        "config-file-provider-global-settings": "org.jenkinsci.plugins.configfiles.maven.job."
125        "MvnGlobalSettingsProvider",
126    }
127
128    if "settings" in data:
129        # Support for Config File Provider
130        settings_file = str(data["settings"])
131        settings_type = data.get("settings-type", "file")
132
133        # For cfp versions <2.10.0 we are able to detect cfp via the config
134        # settings name.
135        text = "org.jenkinsci.plugins.configfiles.maven.MavenSettingsConfig"
136        if settings_file.startswith(text):
137            settings_type = "cfp"
138
139        if settings_type == "file":
140            lsettings = XML.SubElement(
141                xml_parent, "settings", {"class": settings["settings"]}
142            )
143            XML.SubElement(lsettings, "path").text = settings_file
144        elif settings_type == "cfp":
145            lsettings = XML.SubElement(
146                xml_parent,
147                "settings",
148                {"class": settings["config-file-provider-settings"]},
149            )
150            XML.SubElement(lsettings, "settingsConfigId").text = settings_file
151        else:
152            raise InvalidAttributeError("settings-type", settings_type, SETTINGS_TYPES)
153    else:
154        XML.SubElement(xml_parent, "settings", {"class": settings["default-settings"]})
155
156    if "global-settings" in data:
157        # Support for Config File Provider
158        global_settings_file = str(data["global-settings"])
159        global_settings_type = data.get("global-settings-type", "file")
160
161        # For cfp versions <2.10.0 we are able to detect cfp via the config
162        # settings name.
163        text = "org.jenkinsci.plugins.configfiles.maven." "GlobalMavenSettingsConfig"
164        if global_settings_file.startswith(text):
165            global_settings_type = "cfp"
166
167        if global_settings_type == "file":
168            gsettings = XML.SubElement(
169                xml_parent, "globalSettings", {"class": settings["global-settings"]}
170            )
171            XML.SubElement(gsettings, "path").text = global_settings_file
172        elif global_settings_type == "cfp":
173            gsettings = XML.SubElement(
174                xml_parent,
175                "globalSettings",
176                {"class": settings["config-file-provider-global-settings"]},
177            )
178            XML.SubElement(gsettings, "settingsConfigId").text = global_settings_file
179        else:
180            raise InvalidAttributeError(
181                "settings-type", global_settings_type, SETTINGS_TYPES
182            )
183    else:
184        XML.SubElement(
185            xml_parent, "globalSettings", {"class": settings["default-global-settings"]}
186        )
187
188
189def copyartifact_build_selector(xml_parent, data, select_tag="selector"):
190
191    select = data.get("which-build", "last-successful")
192    selectdict = {
193        "last-successful": "StatusBuildSelector",
194        "last-completed": "LastCompletedBuildSelector",
195        "specific-build": "SpecificBuildSelector",
196        "last-saved": "SavedBuildSelector",
197        "upstream-build": "TriggeredBuildSelector",
198        "permalink": "PermalinkBuildSelector",
199        "workspace-latest": "WorkspaceSelector",
200        "build-param": "ParameterizedBuildSelector",
201        "downstream-build": "DownstreamBuildSelector",
202        "multijob-build": "MultiJobBuildSelector",
203    }
204    if select not in selectdict:
205        raise InvalidAttributeError("which-build", select, selectdict.keys())
206    permalink = data.get("permalink", "last")
207    permalinkdict = {
208        "last": "lastBuild",
209        "last-stable": "lastStableBuild",
210        "last-successful": "lastSuccessfulBuild",
211        "last-failed": "lastFailedBuild",
212        "last-unstable": "lastUnstableBuild",
213        "last-unsuccessful": "lastUnsuccessfulBuild",
214    }
215    if permalink not in permalinkdict:
216        raise InvalidAttributeError("permalink", permalink, permalinkdict.keys())
217    if select == "multijob-build":
218        selector = XML.SubElement(
219            xml_parent,
220            select_tag,
221            {"class": "com.tikal.jenkins.plugins.multijob." + selectdict[select]},
222        )
223    else:
224        selector = XML.SubElement(
225            xml_parent,
226            select_tag,
227            {"class": "hudson.plugins.copyartifact." + selectdict[select]},
228        )
229    mapping = []
230    if select == "specific-build":
231        mapping.append(("build-number", "buildNumber", ""))
232    if select == "last-successful":
233        mapping.append(("stable", "stable", False))
234    if select == "upstream-build":
235        mapping.append(
236            ("fallback-to-last-successful", "fallbackToLastSuccessful", False)
237        )
238    if select == "permalink":
239        mapping.append(("", "id", permalinkdict[permalink]))
240    if select == "build-param":
241        mapping.append(("param", "parameterName", ""))
242    if select == "downstream-build":
243        mapping.append(("upstream-project-name", "upstreamProjectName", ""))
244        mapping.append(("upstream-build-number", "upstreamBuildNumber", ""))
245    convert_mapping_to_xml(selector, data, mapping, fail_required=False)
246
247
248def findbugs_settings(xml_parent, data):
249    # General Options
250    mapping = [
251        ("rank-priority", "isRankActivated", False),
252        ("include-files", "includePattern", ""),
253        ("exclude-files", "excludePattern", ""),
254    ]
255    convert_mapping_to_xml(xml_parent, data, mapping, fail_required=True)
256
257
258def get_value_from_yaml_or_config_file(key, section, data, jjb_config):
259    return jjb_config.get_plugin_config(section, key, data.get(key, ""))
260
261
262def cloudformation_region_dict():
263    region_dict = {
264        "us-east-1": "US_East_Northern_Virginia",
265        "us-west-1": "US_WEST_Northern_California",
266        "us-west-2": "US_WEST_Oregon",
267        "eu-central-1": "EU_Frankfurt",
268        "eu-west-1": "EU_Ireland",
269        "ap-southeast-1": "Asia_Pacific_Singapore",
270        "ap-southeast-2": "Asia_Pacific_Sydney",
271        "ap-northeast-1": "Asia_Pacific_Tokyo",
272        "sa-east-1": "South_America_Sao_Paulo",
273    }
274    return region_dict
275
276
277def cloudformation_init(xml_parent, data, xml_tag):
278    cloudformation = XML.SubElement(
279        xml_parent,
280        "com.syncapse.jenkinsci." "plugins.awscloudformationwrapper." + xml_tag,
281    )
282    return XML.SubElement(cloudformation, "stacks")
283
284
285def cloudformation_stack(xml_parent, stack, xml_tag, stacks, region_dict):
286    if "name" not in stack or stack["name"] == "":
287        raise MissingAttributeError("name")
288    step = XML.SubElement(
289        stacks, "com.syncapse.jenkinsci.plugins." "awscloudformationwrapper." + xml_tag
290    )
291
292    if xml_tag == "SimpleStackBean":
293        mapping = [("prefix", "isPrefixSelected", False)]
294    else:
295        parameters_value = ",".join(stack.get("parameters", []))
296        mapping = [
297            ("description", "description", ""),
298            ("", "parameters", parameters_value),
299            ("timeout", "timeout", "0"),
300            ("sleep", "sleep", "0"),
301            ("recipe", "cloudFormationRecipe", None),
302        ]
303
304    cloudformation_stack_mapping = [
305        ("name", "stackName", None),
306        ("access-key", "awsAccessKey", None),
307        ("secret-key", "awsSecretKey", None),
308        ("region", "awsRegion", None, region_dict),
309    ]
310    for map in mapping:
311        cloudformation_stack_mapping.append(map)
312
313    convert_mapping_to_xml(
314        step, stack, cloudformation_stack_mapping, fail_required=True
315    )
316
317
318def include_exclude_patterns(xml_parent, data, yaml_prefix, xml_elem_name):
319    xml_element = XML.SubElement(xml_parent, xml_elem_name)
320    XML.SubElement(xml_element, "includePatterns").text = ",".join(
321        data.get(yaml_prefix + "-include-patterns", [])
322    )
323    XML.SubElement(xml_element, "excludePatterns").text = ",".join(
324        data.get(yaml_prefix + "-exclude-patterns", [])
325    )
326
327
328def artifactory_deployment_patterns(xml_parent, data):
329    include_exclude_patterns(
330        xml_parent, data, "deployment", "artifactDeploymentPatterns"
331    )
332
333
334def artifactory_env_vars_patterns(xml_parent, data):
335    include_exclude_patterns(xml_parent, data, "env-vars", "envVarsPatterns")
336
337
338def artifactory_optional_props(xml_parent, data, target):
339    optional_str_props = [
340        ("scopes", "scopes"),
341        ("violationRecipients", "violation-recipients"),
342        ("blackDuckAppName", "black-duck-app-name"),
343        ("blackDuckAppVersion", "black-duck-app-version"),
344        ("blackDuckReportRecipients", "black-duck-report-recipients"),
345        ("blackDuckScopes", "black-duck-scopes"),
346    ]
347
348    for (xml_prop, yaml_prop) in optional_str_props:
349        XML.SubElement(xml_parent, xml_prop).text = data.get(yaml_prop, "")
350
351    common_bool_props = [
352        # yaml property name, xml property name, default value
353        ("deploy-artifacts", "deployArtifacts", True),
354        ("discard-old-builds", "discardOldBuilds", False),
355        ("discard-build-artifacts", "discardBuildArtifacts", False),
356        ("publish-build-info", "deployBuildInfo", False),
357        ("env-vars-include", "includeEnvVars", False),
358        ("run-checks", "runChecks", False),
359        ("include-publish-artifacts", "includePublishArtifacts", False),
360        ("license-auto-discovery", "licenseAutoDiscovery", True),
361        ("enable-issue-tracker-integration", "enableIssueTrackerIntegration", False),
362        ("aggregate-build-issues", "aggregateBuildIssues", False),
363        ("black-duck-run-checks", "blackDuckRunChecks", False),
364        (
365            "black-duck-include-published-artifacts",
366            "blackDuckIncludePublishedArtifacts",
367            False,
368        ),
369        (
370            "auto-create-missing-component-requests",
371            "autoCreateMissingComponentRequests",
372            True,
373        ),
374        (
375            "auto-discard-stale-component-requests",
376            "autoDiscardStaleComponentRequests",
377            True,
378        ),
379        (
380            "filter-excluded-artifacts-from-build",
381            "filterExcludedArtifactsFromBuild",
382            False,
383        ),
384    ]
385    convert_mapping_to_xml(xml_parent, data, common_bool_props, fail_required=True)
386
387    if "wrappers" in target:
388        wrapper_bool_props = [
389            ("enable-resolve-artifacts", "enableResolveArtifacts", False),
390            ("disable-license-auto-discovery", "disableLicenseAutoDiscovery", False),
391            ("record-all-dependencies", "recordAllDependencies", False),
392        ]
393        convert_mapping_to_xml(xml_parent, data, wrapper_bool_props, fail_required=True)
394
395    if "publishers" in target:
396        publisher_bool_props = [
397            ("even-if-unstable", "evenIfUnstable", False),
398            ("pass-identified-downstream", "passIdentifiedDownstream", False),
399            (
400                "allow-promotion-of-non-staged-builds",
401                "allowPromotionOfNonStagedBuilds",
402                False,
403            ),
404        ]
405        convert_mapping_to_xml(
406            xml_parent, data, publisher_bool_props, fail_required=True
407        )
408
409
410def artifactory_common_details(details, data):
411    mapping = [("name", "artifactoryName", ""), ("url", "artifactoryUrl", "")]
412    convert_mapping_to_xml(details, data, mapping, fail_required=True)
413
414
415def artifactory_repository(xml_parent, data, target):
416    if "release" in target:
417        release_mapping = [
418            ("deploy-release-repo-key", "keyFromText", ""),
419            ("deploy-release-repo-key", "keyFromSelect", ""),
420            ("deploy-dynamic-mode", "dynamicMode", False),
421        ]
422        convert_mapping_to_xml(xml_parent, data, release_mapping, fail_required=True)
423
424    if "snapshot" in target:
425        snapshot_mapping = [
426            ("deploy-snapshot-repo-key", "keyFromText", ""),
427            ("deploy-snapshot-repo-key", "keyFromSelect", ""),
428            ("deploy-dynamic-mode", "dynamicMode", False),
429        ]
430        convert_mapping_to_xml(xml_parent, data, snapshot_mapping, fail_required=True)
431
432
433def append_git_revision_config(parent, config_def):
434    params = XML.SubElement(parent, "hudson.plugins.git.GitRevisionBuildParameters")
435
436    try:
437        # If git-revision is a boolean, the get() will
438        # throw an AttributeError
439        combine_commits = str(config_def.get("combine-queued-commits", False)).lower()
440    except AttributeError:
441        combine_commits = "false"
442
443    XML.SubElement(params, "combineQueuedCommits").text = combine_commits
444
445
446def test_fairy_common(xml_element, data):
447    xml_element.set("plugin", "TestFairy")
448    valid_max_duration = ["10m", "60m", "300m", "1440m"]
449    valid_interval = [1, 2, 5]
450    valid_video_quality = ["high", "medium", "low"]
451
452    mappings = [
453        # General
454        ("apikey", "apiKey", None),
455        ("appfile", "appFile", None),
456        ("tester-groups", "testersGroups", ""),
457        ("notify-testers", "notifyTesters", True),
458        ("autoupdate", "autoUpdate", True),
459        # Session
460        ("max-duration", "maxDuration", "10m", valid_max_duration),
461        ("record-on-background", "recordOnBackground", False),
462        ("data-only-wifi", "dataOnlyWifi", False),
463        # Video
464        ("video-enabled", "isVideoEnabled", True),
465        ("screenshot-interval", "screenshotInterval", 1, valid_interval),
466        ("video-quality", "videoQuality", "high", valid_video_quality),
467        # Metrics
468        ("cpu", "cpu", True),
469        ("memory", "memory", True),
470        ("logs", "logs", True),
471        ("network", "network", False),
472        ("phone-signal", "phoneSignal", False),
473        ("wifi", "wifi", False),
474        ("gps", "gps", False),
475        ("battery", "battery", False),
476        ("opengl", "openGl", False),
477        # Advanced options
478        ("advanced-options", "advancedOptions", ""),
479    ]
480    convert_mapping_to_xml(xml_element, data, mappings, fail_required=True)
481
482
483def trigger_get_parameter_order(registry, plugin):
484    logger = logging.getLogger("%s:trigger_get_parameter_order" % __name__)
485
486    if (
487        str(
488            registry.jjb_config.get_plugin_config(plugin, "param_order_from_yaml", True)
489        ).lower()
490        == "false"
491    ):
492        logger.warning(
493            "Using deprecated order for parameter sets in %s. It is "
494            "recommended that you update your job definition instead of "
495            "enabling use of the old hardcoded order",
496            plugin,
497        )
498
499        # deprecated order
500        return [
501            "predefined-parameters",
502            "git-revision",
503            "property-file",
504            "current-parameters",
505            "node-parameters",
506            "svn-revision",
507            "restrict-matrix-project",
508            "node-label-name",
509            "node-label",
510            "boolean-parameters",
511        ]
512
513    return None
514
515
516def trigger_project(tconfigs, project_def, registry, param_order=None):
517
518    info = registry.get_plugin_info("parameterized-trigger")
519    plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize)))
520
521    logger = logging.getLogger("%s:trigger_project" % __name__)
522    pt_prefix = "hudson.plugins.parameterizedtrigger."
523    if param_order:
524        parameters = param_order
525    else:
526        parameters = project_def.keys()
527
528    for param_type in parameters:
529        param_value = project_def.get(param_type)
530        if param_value is None:
531            continue
532
533        if param_type == "predefined-parameters":
534            params = XML.SubElement(tconfigs, pt_prefix + "PredefinedBuildParameters")
535            properties = XML.SubElement(params, "properties")
536            properties.text = param_value
537        elif param_type == "git-revision" and param_value:
538            if "combine-queued-commits" in project_def:
539                logger.warning(
540                    "'combine-queued-commit' has moved to reside under "
541                    "'git-revision' configuration, please update your "
542                    "configs as support for this will be removed."
543                )
544                git_revision = {
545                    "combine-queued-commits": project_def["combine-queued-commits"]
546                }
547            else:
548                git_revision = project_def["git-revision"]
549            append_git_revision_config(tconfigs, git_revision)
550        elif param_type == "property-file":
551            params = XML.SubElement(tconfigs, pt_prefix + "FileBuildParameters")
552            property_file_mapping = [
553                ("property-file", "propertiesFile", None),
554                ("fail-on-missing", "failTriggerOnMissing", False),
555            ]
556
557            if plugin_version >= pkg_resources.parse_version("2.35.2"):
558                property_file_mapping.append(
559                    ("property-multiline", "textParamValueOnNewLine", False)
560                )
561
562            convert_mapping_to_xml(
563                params, project_def, property_file_mapping, fail_required=True
564            )
565            if "file-encoding" in project_def:
566                XML.SubElement(params, "encoding").text = project_def["file-encoding"]
567            if "use-matrix-child-files" in project_def:
568                # TODO: These parameters only affect execution in
569                # publishers of matrix projects; we should warn if they are
570                # used in other contexts.
571                use_matrix_child_files_mapping = [
572                    ("use-matrix-child-files", "useMatrixChild", None),
573                    ("matrix-child-combination-filter", "combinationFilter", ""),
574                    ("only-exact-matrix-child-runs", "onlyExactRuns", False),
575                ]
576                convert_mapping_to_xml(
577                    params,
578                    project_def,
579                    use_matrix_child_files_mapping,
580                    fail_required=True,
581                )
582        elif param_type == "current-parameters" and param_value:
583            XML.SubElement(tconfigs, pt_prefix + "CurrentBuildParameters")
584        elif param_type == "node-parameters" and param_value:
585            XML.SubElement(tconfigs, pt_prefix + "NodeParameters")
586        elif param_type == "svn-revision" and param_value:
587            param = XML.SubElement(
588                tconfigs, pt_prefix + "SubversionRevisionBuildParameters"
589            )
590            XML.SubElement(param, "includeUpstreamParameters").text = str(
591                project_def.get("include-upstream", False)
592            ).lower()
593        elif param_type == "restrict-matrix-project" and param_value:
594            subset = XML.SubElement(
595                tconfigs, pt_prefix + "matrix.MatrixSubsetBuildParameters"
596            )
597            XML.SubElement(subset, "filter").text = project_def[
598                "restrict-matrix-project"
599            ]
600        elif param_type == "node-label-name" or param_type == "node-label":
601            tag_name = (
602                "org.jvnet.jenkins.plugins.nodelabelparameter."
603                "parameterizedtrigger.NodeLabelBuildParameter"
604            )
605            if tconfigs.find(tag_name) is not None:
606                # already processed and can only have one
607                continue
608            params = XML.SubElement(tconfigs, tag_name)
609            name = XML.SubElement(params, "name")
610            if "node-label-name" in project_def:
611                name.text = project_def["node-label-name"]
612            label = XML.SubElement(params, "nodeLabel")
613            if "node-label" in project_def:
614                label.text = project_def["node-label"]
615        elif param_type == "boolean-parameters" and param_value:
616            params = XML.SubElement(tconfigs, pt_prefix + "BooleanParameters")
617            config_tag = XML.SubElement(params, "configs")
618            param_tag_text = pt_prefix + "BooleanParameterConfig"
619            params_list = param_value
620            for name, value in params_list.items():
621                param_tag = XML.SubElement(config_tag, param_tag_text)
622                mapping = [("", "name", name), ("", "value", value or False)]
623                convert_mapping_to_xml(
624                    param_tag, project_def, mapping, fail_required=True
625                )
626
627
628def trigger_threshold(
629    parent_element, element_name, threshold_name, supported_thresholds=None
630):
631    """Generate a resultThreshold XML element for build/join triggers"""
632    element = XML.SubElement(parent_element, element_name)
633
634    try:
635        threshold = hudson_model.THRESHOLDS[threshold_name.upper()]
636    except KeyError:
637        if not supported_thresholds:
638            supported_thresholds = hudson_model.THRESHOLDS.keys()
639        raise JenkinsJobsException(
640            "threshold must be one of %s" % ", ".join(supported_thresholds)
641        )
642    XML.SubElement(element, "name").text = threshold["name"]
643    XML.SubElement(element, "ordinal").text = threshold["ordinal"]
644    XML.SubElement(element, "color").text = threshold["color"]
645    return element
646
647
648def convert_mapping_to_xml(parent, data, mapping, fail_required=True):
649    """Convert mapping to XML
650
651    fail_required affects the last parameter of the mapping field when it's
652    parameter is set to 'None'. When fail_required is True then a 'None' value
653    represents a required configuration so will raise a MissingAttributeError
654    if the user does not provide the configuration.
655
656    If fail_required is False parameter is treated as optional. Logic will skip
657    configuring the XML tag for the parameter. We recommend for new plugins to
658    set fail_required=True and instead of optional parameters provide a default
659    value for all parameters that are not required instead.
660
661    valid_options provides a way to check if the value the user input is from a
662    list of available options. When the user pass a value that is not supported
663    from the list, it raise an InvalidAttributeError.
664
665    valid_dict provides a way to set options through their key and value. If
666    the user input corresponds to a key, the XML tag will use the key's value
667    for its element. When the user pass a value that there are no keys for,
668    it raise an InvalidAttributeError.
669    """
670    for elem in mapping:
671        (optname, xmlname, val) = elem[:3]
672        val = data.get(optname, val)
673
674        valid_options = []
675        valid_dict = {}
676        if len(elem) == 4:
677            if type(elem[3]) is list:
678                valid_options = elem[3]
679            if type(elem[3]) is dict:
680                valid_dict = elem[3]
681
682        # Use fail_required setting to allow support for optional parameters
683        if val is None and fail_required is True:
684            raise MissingAttributeError(optname)
685
686        # if no value is provided then continue else leave it
687        # up to the user if they want to use an empty XML tag
688        if val is None and fail_required is False:
689            continue
690
691        if valid_dict:
692            if val not in valid_dict:
693                raise InvalidAttributeError(optname, val, valid_dict.keys())
694
695        if valid_options:
696            if val not in valid_options:
697                raise InvalidAttributeError(optname, val, valid_options)
698
699        if type(val) == bool:
700            val = str(val).lower()
701
702        if val in valid_dict:
703            XML.SubElement(parent, xmlname).text = str(valid_dict[val])
704        else:
705            XML.SubElement(parent, xmlname).text = str(val)
706
707
708def jms_messaging_common(parent, subelement, data):
709    """JMS common helper function
710
711    Pass the XML parent and the specific subelement from the builder or the
712    publisher.
713
714    data is passed to mapper helper function to map yaml fields to XML fields
715    """
716    namespace = XML.SubElement(parent, subelement)
717
718    if "override-topic" in data:
719        overrides = XML.SubElement(namespace, "overrides")
720        XML.SubElement(overrides, "topic").text = str(data.get("override-topic", ""))
721
722    mapping = [
723        # option, xml name, default value
724        ("provider-name", "providerName", ""),
725        ("msg-type", "messageType", "CodeQualityChecksDone"),
726        ("msg-props", "messageProperties", ""),
727        ("msg-content", "messageContent", ""),
728    ]
729    convert_mapping_to_xml(namespace, data, mapping, fail_required=True)
730
731
732def check_mutual_exclusive_data_args(data_arg_position, *args):
733    mutual_exclusive_args = set(args)
734
735    def validator(f):
736        @wraps(f)
737        def wrap(*args):
738            actual_args = set(args[data_arg_position].keys())
739            if len(actual_args & mutual_exclusive_args) > 1:
740                raise JenkinsJobsException(
741                    "Args: {} in {} are mutual exclusive. Please define one of it.".format(
742                        mutual_exclusive_args, f.__name__
743                    )
744                )
745            return f(*args)
746
747        return wrap
748
749    return validator
750