示例#1
0
    def build_condition(cdata):
        kind = cdata['condition-kind']
        ctag = XML.SubElement(root_tag, condition_tag)
        if kind == "always":
            ctag.set('class',
                     'org.jenkins_ci.plugins.run_condition.core.AlwaysRun')
        elif kind == "never":
            ctag.set('class',
                     'org.jenkins_ci.plugins.run_condition.core.NeverRun')
        elif kind == "boolean-expression":
            ctag.set(
                'class', 'org.jenkins_ci.plugins.run_condition.core.'
                'BooleanCondition')
            XML.SubElement(ctag, "token").text = cdata['condition-expression']
        elif kind == "current-status":
            ctag.set(
                'class', 'org.jenkins_ci.plugins.run_condition.core.'
                'StatusCondition')
            wr = XML.SubElement(ctag, 'worstResult')
            wr_name = cdata['condition-worst']
            if wr_name not in hudson_model.THRESHOLDS:
                raise JenkinsJobsException(
                    "threshold must be one of %s" %
                    ", ".join(hudson_model.THRESHOLDS.keys()))
            wr_threshold = hudson_model.THRESHOLDS[wr_name]
            XML.SubElement(wr, "name").text = wr_threshold['name']
            XML.SubElement(wr, "ordinal").text = wr_threshold['ordinal']
            XML.SubElement(wr, "color").text = wr_threshold['color']
            XML.SubElement(wr, "completeBuild").text = \
                str(wr_threshold['complete']).lower()

            br = XML.SubElement(ctag, 'bestResult')
            br_name = cdata['condition-best']
            if not br_name in hudson_model.THRESHOLDS:
                raise JenkinsJobsException(
                    "threshold must be one of %s" %
                    ", ".join(hudson_model.THRESHOLDS.keys()))
            br_threshold = hudson_model.THRESHOLDS[br_name]
            XML.SubElement(br, "name").text = br_threshold['name']
            XML.SubElement(br, "ordinal").text = br_threshold['ordinal']
            XML.SubElement(br, "color").text = br_threshold['color']
            XML.SubElement(br, "completeBuild").text = \
                str(wr_threshold['complete']).lower()
        elif kind == "shell":
            ctag.set(
                'class', 'org.jenkins_ci.plugins.run_condition.contributed.'
                'ShellCondition')
            XML.SubElement(ctag, "command").text = cdata['condition-command']
        elif kind == "windows-shell":
            ctag.set(
                'class', 'org.jenkins_ci.plugins.run_condition.contributed.'
                'BatchFileCondition')
            XML.SubElement(ctag, "command").text = cdata['condition-command']
        elif kind == "file-exists":
            ctag.set(
                'class', 'org.jenkins_ci.plugins.run_condition.core.'
                'FileExistsCondition')
            XML.SubElement(ctag, "file").text = cdata['condition-filename']
            basedir = cdata.get('condition-basedir', 'workspace')
            basedir_tag = XML.SubElement(ctag, "baseDir")
            if "workspace" == basedir:
                basedir_tag.set(
                    'class', 'org.jenkins_ci.plugins.run_condition.common.'
                    'BaseDirectory$Workspace')
            elif "artifact-directory" == basedir:
                basedir_tag.set(
                    'class', 'org.jenkins_ci.plugins.run_condition.common.'
                    'BaseDirectory$ArtifactsDir')
            elif "jenkins-home" == basedir:
                basedir_tag.set(
                    'class', 'org.jenkins_ci.plugins.run_condition.common.'
                    'BaseDirectory$JenkinsHome')
示例#2
0
def build_trends_publisher(plugin_name, xml_element, data):
    """Helper to create various trend publishers.
    """

    def append_thresholds(element, data, only_totals):
        """Appends the status thresholds.
        """

        for status in ['unstable', 'failed']:
            status_data = data.get(status, {})

            limits = [
                ('total-all', 'TotalAll'),
                ('total-high', 'TotalHigh'),
                ('total-normal', 'TotalNormal'),
                ('total-low', 'TotalLow')]

            if only_totals is False:
                limits.extend([
                    ('new-all', 'NewAll'),
                    ('new-high', 'NewHigh'),
                    ('new-normal', 'NewNormal'),
                    ('new-low', 'NewLow')])

            for key, tag_suffix in limits:
                tag_name = status + tag_suffix
                XML.SubElement(element, tag_name).text = str(
                    status_data.get(key, ''))

    # Tuples containing: setting name, tag name, default value
    settings = [
        ('healthy', 'healthy', ''),
        ('unhealthy', 'unHealthy', ''),
        ('health-threshold', 'thresholdLimit', 'low'),
        ('plugin-name', 'pluginName', plugin_name),
        ('default-encoding', 'defaultEncoding', ''),
        ('can-run-on-failed', 'canRunOnFailed', False),
        ('use-stable-build-as-reference', 'useStableBuildAsReference', False),
        ('use-delta-values', 'useDeltaValues', False),
        ('thresholds', 'thresholds', {}),
        ('should-detect-modules', 'shouldDetectModules', False),
        ('dont-compute-new', 'dontComputeNew', True),
        ('do-not-resolve-relative-paths', 'doNotResolveRelativePaths', False),
        ('pattern', 'pattern', '')]

    thresholds = ['low', 'normal', 'high']

    for key, tag_name, default in settings:
        xml_config = XML.SubElement(xml_element, tag_name)
        config_value = data.get(key, default)

        if key == 'thresholds':
            append_thresholds(
                xml_config,
                config_value,
                data.get('dont-compute-new', True))
        elif key == 'health-threshold' and config_value not in thresholds:
            raise JenkinsJobsException("health-threshold must be one of %s" %
                                       ", ".join(thresholds))
        elif key == 'use-stable-build-as-reference' \
            and '[DependencyCheck]' in plugin_name:
                XML.SubElement(xml_element, 'usePreviousBuildAsReference') \
                    .text = \
                    str(data.get('use-previous-build-as-reference', False)) \
                    .lower()
                xml_config.text = str(config_value).lower()
        else:
            if isinstance(default, bool):
                xml_config.text = str(config_value).lower()
            else:
                xml_config.text = str(config_value)
示例#3
0
    def generateXML(self, jobs_filter=None):
        changed = True
        while changed:
            changed = False
            for module in self.registry.modules:
                if hasattr(module, 'handle_data'):
                    if module.handle_data(self):
                        changed = True

        for job in self.data.get('job', {}).values():
            if jobs_filter and not matches(job['name'], jobs_filter):
                logger.debug("Ignoring job {0}".format(job['name']))
                continue
            logger.debug("XMLifying job '{0}'".format(job['name']))
            job = self.applyDefaults(job)
            self.getXMLForJob(job)
        for project in self.data.get('project', {}).values():
            logger.debug("XMLifying project '{0}'".format(project['name']))
            for jobspec in project.get('jobs', []):
                if isinstance(jobspec, dict):
                    # Singleton dict containing dict of job-specific params
                    jobname, jobparams = jobspec.items()[0]
                    if not isinstance(jobparams, dict):
                        jobparams = {}
                else:
                    jobname = jobspec
                    jobparams = {}
                job = self.getJob(jobname)
                if job:
                    # Just naming an existing defined job
                    continue
                # see if it's a job group
                group = self.getJobGroup(jobname)
                if group:
                    for group_jobspec in group['jobs']:
                        if isinstance(group_jobspec, dict):
                            group_jobname, group_jobparams = \
                                group_jobspec.items()[0]
                            if not isinstance(group_jobparams, dict):
                                group_jobparams = {}
                        else:
                            group_jobname = group_jobspec
                            group_jobparams = {}
                        job = self.getJob(group_jobname)
                        if job:
                            continue
                        template = self.getJobTemplate(group_jobname)
                        # Allow a group to override parameters set by a project
                        d = {}
                        d.update(project)
                        d.update(jobparams)
                        d.update(group)
                        d.update(group_jobparams)
                        # Except name, since the group's name is not useful
                        d['name'] = project['name']
                        if template:
                            self.getXMLForTemplateJob(d, template, jobs_filter)
                    continue
                # see if it's a template
                template = self.getJobTemplate(jobname)
                if template:
                    d = {}
                    d.update(project)
                    d.update(jobparams)
                    self.getXMLForTemplateJob(d, template, jobs_filter)
                else:
                    raise JenkinsJobsException(
                        "Failed to find suitable "
                        "template named '{0}'".format(jobname))
示例#4
0
def execute(options, config):
    logger.debug("Config: {0}".format(config))

    # check the ignore_cache setting: first from command line,
    # if not present check from ini file
    ignore_cache = False
    if options.ignore_cache:
        ignore_cache = options.ignore_cache
    elif config.has_option('jenkins', 'ignore_cache'):
        logging.warn('ignore_cache option should be moved to the [job_builder]'
                     ' section in the config file, the one specified in the '
                     '[jenkins] section will be ignored in the future')
        ignore_cache = config.getboolean('jenkins', 'ignore_cache')
    elif config.has_option('job_builder', 'ignore_cache'):
        ignore_cache = config.getboolean('job_builder', 'ignore_cache')

    # Jenkins supports access as an anonymous user, which can be used to
    # ensure read-only behaviour when querying the version of plugins
    # installed for test mode to generate XML output matching what will be
    # uploaded. To enable must pass 'None' as the value for user and password
    # to python-jenkins
    #
    # catching 'TypeError' is a workaround for python 2.6 interpolation error
    # https://bugs.launchpad.net/openstack-ci/+bug/1259631
    try:
        user = config.get('jenkins', 'user')
    except (TypeError, configparser.NoOptionError):
        user = None

    try:
        password = config.get('jenkins', 'password')
    except (TypeError, configparser.NoOptionError):
        password = None

    # Inform the user as to what is likely to happen, as they may specify
    # a real jenkins instance in test mode to get the plugin info to check
    # the XML generated.
    if user is None and password is None:
        logger.info("Will use anonymous access to Jenkins if needed.")
    elif (user is not None and password is None) or (
            user is None and password is not None):
        raise JenkinsJobsException(
            "Cannot authenticate to Jenkins with only one of User and "
            "Password provided, please check your configuration."
        )

    # None -- no timeout, blocking mode; same as setblocking(True)
    # 0.0 -- non-blocking mode; same as setblocking(False) <--- default
    # > 0 -- timeout mode; operations time out after timeout seconds
    # < 0 -- illegal; raises an exception
    # to retain the default must use
    # "timeout=jenkins_jobs.builder._DEFAULT_TIMEOUT" or not set timeout at
    # all.
    timeout = jenkins_jobs.builder._DEFAULT_TIMEOUT
    try:
        timeout = config.getfloat('jenkins', 'timeout')
    except (ValueError):
        raise JenkinsJobsException("Jenkins timeout config is invalid")
    except (TypeError, configparser.NoOptionError):
        pass

    plugins_info = None

    if getattr(options, 'plugins_info_path', None) is not None:
        with io.open(options.plugins_info_path, 'r',
                     encoding='utf-8') as yaml_file:
            plugins_info = yaml.load(yaml_file)
        if not isinstance(plugins_info, list):
            raise JenkinsJobsException("{0} must contain a Yaml list!"
                                       .format(options.plugins_info_path))
    elif (not options.conf or not
          config.getboolean("jenkins", "query_plugins_info")):
        logger.debug("Skipping plugin info retrieval")
        plugins_info = {}

    if options.allow_empty_variables is not None:
        config.set('job_builder',
                   'allow_empty_variables',
                   str(options.allow_empty_variables))

    builder = Builder(config.get('jenkins', 'url'),
                      user,
                      password,
                      config,
                      jenkins_timeout=timeout,
                      ignore_cache=ignore_cache,
                      flush_cache=options.flush_cache,
                      plugins_list=plugins_info)

    if getattr(options, 'path', None):
        if hasattr(options.path, 'read'):
            logger.debug("Input file is stdin")
            if options.path.isatty():
                key = 'CTRL+Z' if platform.system() == 'Windows' else 'CTRL+D'
                logger.warn(
                    "Reading configuration from STDIN. Press %s to end input.",
                    key)
        else:
            # take list of paths
            options.path = options.path.split(os.pathsep)

            do_recurse = (getattr(options, 'recursive', False) or
                          config.getboolean('job_builder', 'recursive'))

            excludes = [e for elist in options.exclude
                        for e in elist.split(os.pathsep)] or \
                config.get('job_builder', 'exclude').split(os.pathsep)
            paths = []
            for path in options.path:
                if do_recurse and os.path.isdir(path):
                    paths.extend(recurse_path(path, excludes))
                else:
                    paths.append(path)
            options.path = paths

    if options.command == 'delete':
        for job in options.name:
            builder.delete_job(job, options.path)
    elif options.command == 'delete-all':
        confirm('Sure you want to delete *ALL* jobs from Jenkins server?\n'
                '(including those not managed by Jenkins Job Builder)')
        logger.info("Deleting all jobs")
        builder.delete_all_jobs()
    elif options.command == 'update':
        if options.n_workers < 0:
            raise JenkinsJobsException(
                'Number of workers must be equal or greater than 0')

        logger.info("Updating jobs in {0} ({1})".format(
            options.path, options.names))
        jobs, num_updated_jobs = builder.update_jobs(
            options.path, options.names,
            n_workers=options.n_workers)
        logger.info("Number of jobs updated: %d", num_updated_jobs)
        if options.delete_old:
            num_deleted_jobs = builder.delete_old_managed()
            logger.info("Number of jobs deleted: %d", num_deleted_jobs)
    elif options.command == 'test':
        builder.update_jobs(options.path, options.name,
                            output=options.output_dir,
                            n_workers=1)
示例#5
0
def execute(options, config):
    logger.debug("Config: {0}".format(config))

    # check the ignore_cache setting: first from command line,
    # if not present check from ini file
    ignore_cache = False
    if options.ignore_cache:
        ignore_cache = options.ignore_cache
    elif config.has_option('jenkins', 'ignore_cache'):
        logging.warn('ignore_cache option should be moved to the [job_builder]'
                     ' section in the config file, the one specified in the '
                     '[jenkins] section will be ignored in the future')
        ignore_cache = config.getboolean('jenkins', 'ignore_cache')
    elif config.has_option('job_builder', 'ignore_cache'):
        ignore_cache = config.getboolean('job_builder', 'ignore_cache')

    # workaround for python 2.6 interpolation error
    # https://bugs.launchpad.net/openstack-ci/+bug/1259631
    try:
        user = config.get('jenkins', 'user')
    except (TypeError, configparser.NoOptionError):
        user = None
    try:
        password = config.get('jenkins', 'password')
    except (TypeError, configparser.NoOptionError):
        password = None

    plugins_info = None

    if getattr(options, 'plugins_info_path', None) is not None:
        with open(options.plugins_info_path, 'r') as yaml_file:
            plugins_info = yaml.load(yaml_file)
        if not isinstance(plugins_info, list):
            raise JenkinsJobsException("{0} must contain a Yaml list!".format(
                options.plugins_info_path))
    elif (not options.conf
          or not config.getboolean("jenkins", "query_plugins_info")):
        logger.debug("Skipping plugin info retrieval")
        plugins_info = {}

    if options.allow_empty_variables is not None:
        config.set('job_builder', 'allow_empty_variables',
                   str(options.allow_empty_variables))

    builder = Builder(config.get('jenkins', 'url'),
                      user,
                      password,
                      config,
                      ignore_cache=ignore_cache,
                      flush_cache=options.flush_cache,
                      plugins_list=plugins_info)

    if getattr(options, 'path', None):
        if options.path == sys.stdin:
            logger.debug("Input file is stdin")
            if options.path.isatty():
                key = 'CTRL+Z' if platform.system() == 'Windows' else 'CTRL+D'
                logger.warn(
                    "Reading configuration from STDIN. Press %s to end input.",
                    key)

        # take list of paths
        options.path = options.path.split(os.pathsep)

        do_recurse = (getattr(options, 'recursive', False)
                      or config.getboolean('job_builder', 'recursive'))

        excludes = [e for elist in options.exclude
                    for e in elist.split(os.pathsep)] or \
            config.get('job_builder', 'exclude').split(os.pathsep)
        paths = []
        for path in options.path:
            if do_recurse and os.path.isdir(path):
                paths.extend(recurse_path(path, excludes))
            else:
                paths.append(path)
        options.path = paths

    if options.command == 'delete':
        if options.del_views:
            for view in options.name:
                builder.delete_view(view, options.path)
        else:
            for job in options.name:
                builder.delete_job(job, options.path)

    elif options.command == 'delete-all':
        deleting_jobs = False
        deleting_views = False
        if options.only_jobs == options.only_views:
            # if (delete-all --jobs --views) or (delete-all)
            # Equivalent of {NOT(--jobs XOR --views)} only passes if both
            # subparameters are true or both false
            reach = 'jobs AND views'
            deleting_jobs = True
            deleting_views = True
        elif options.only_jobs and not options.only_views:
            reach = 'jobs'
            deleting_jobs = True
        elif options.only_views and not options.only_jobs:
            reach = 'views'
            deleting_views = True
        confirm(
            'Sure you want to delete *ALL* %s from Jenkins server?\n'
            '(including those not managed by Jenkins Job Builder)', reach)
        if deleting_jobs:
            logger.info("Deleting all jobs")
            builder.delete_all_jobs()
        if deleting_views:
            logger.info("Deleting all views")
            builder.delete_all_views()

    elif options.command == 'update':
        logger.info("Updating jobs in {0} ({1})".format(
            options.path, options.names))
        jobs, views, num_updated_jobs, num_updated_views = builder.update_job(
            options.path, options.names)
        logger.info("%d jobs updated, %d views updated", num_updated_jobs,
                    num_updated_views)
        if options.delete_old:
            num_deleted_jobs = builder.delete_old_managed()
            logger.info("Number of jobs deleted: %d", num_deleted_jobs)
    elif options.command == 'test':
        builder.update_job(options.path,
                           options.name,
                           output=options.output_dir)
def extended_choice(parser, xml_parent, data):
    """yaml: extended-choice
    Creates an extended choice property where values can be read from a file
    Requires the Jenkins `Extended Choice Parameter Plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/
    Extended+Choice+Parameter+plugin>`_

    :arg string name: name of the property
    :arg string description: description of the property (optional, default '')
    :arg string property-file: location of property file to read from
        (optional, default '')
    :arg string property-key: key for the property-file (optional, default '')
    :arg bool quote-value: whether to put quotes around the property
        when passing to Jenkins (optional, default false)
    :arg string visible-items: number of items to show in the list
        (optional, default 5)
    :arg string type: type of select (optional, default single-select)
    :arg string value: comma separated list of values for the single select
        or multi-select box (optional, default '')
    :arg string default-value: used to set the initial selection of the
        single-select or multi-select box (optional, default '')
    :arg string default-property-file: location of property file when default
        value needs to come from a property file (optional, default '')
    :arg string default-property-key: key for the default property file
        (optional, default '')

    Example::

      properties:
        - extended-choice:
            name: FOO
            description: A foo property
            property-file: /home/foo/property.prop
            property-key: key
            quote-value: true
            visible-items: 10
            type: multi-select
            value: foo,bar,select
            default-value: foo
            default-property-file: /home/property.prop
            default-property-key: fookey
    """
    definition = XML.SubElement(xml_parent,
                                'hudson.model.ParametersDefinitionProperty')
    definitions = XML.SubElement(definition, 'parameterDefinitions')
    extended = XML.SubElement(definitions, 'com.cwctravel.hudson.plugins.'
                                           'extended__choice__parameter.'
                                           'ExtendedChoiceParameterDefinition')
    XML.SubElement(extended, 'name').text = data['name']
    XML.SubElement(extended, 'description').text = data.get('description', '')
    XML.SubElement(extended, 'quoteValue').text = str(data.get('quote-value',
                                                      False)).lower()
    XML.SubElement(extended, 'visibleItemCount').text = data.get(
        'visible-items', '5')
    choice = data.get('type', 'single-select')
    choicedict = {'single-select': 'PT_SINGLE_SELECT',
                  'multi-select': 'PT_MULTI_SELECT',
                  'radio': 'PT_RADIO',
                  'checkbox': 'PT_CHECKBOX'}
    if choice not in choicedict:
        raise JenkinsJobsException("Type entered is not valid, must be one "
                                   "of: single-select, multi-select, radio, "
                                   "or checkbox")
    XML.SubElement(extended, 'type').text = choicedict[choice]
    XML.SubElement(extended, 'value').text = data.get('value', '')
    XML.SubElement(extended, 'propertyFile').text = data.get('property-file',
                                                             '')
    XML.SubElement(extended, 'propertyKey').text = data.get('property-key', '')
    XML.SubElement(extended, 'defaultValue').text = data.get('default-value',
                                                             '')
    XML.SubElement(extended, 'defaultPropertyFile').text = data.get(
        'default-property-file', '')
    XML.SubElement(extended, 'defaultPropertyKey').text = data.get(
        'default-property-key', '')
示例#7
0
    def dispatch(self,
                 component_type,
                 xml_parent,
                 component,
                 template_data={}):
        """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
        entry points and Jenkins Job Builder :ref:`Macros <macro>`.

        :arg string component_type: the name of the component
          (e.g., `builder`)
        :arg YAMLParser parser: the global YAML Parser
        :arg Element xml_parent: the parent XML element
        :arg dict template_data: values that should be interpolated into
          the component definition

        See :py:class:`jenkins_jobs.modules.base.Base` for how to register
        components of a module.

        See the Publishers module for a simple example of how to use
        this method.
        """

        if component_type not in self.modules_by_component_type:
            raise JenkinsJobsException("Unknown component type: "
                                       "'{0}'.".format(component_type))

        entry_point = self.modules_by_component_type[component_type]
        component_list_type = self.get_component_list_type(entry_point)

        if isinstance(component, dict):
            # 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):
                # 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,
                        self.jjb_config.yamlparser["allow_empty_variables"],
                    )
                except Exception:
                    logging.error(
                        "Failure formatting component ('%s') data '%s'",
                        name,
                        component_data,
                    )
                    raise
        else:
            # The component is a simple string name, eg "run-tests"
            name = component
            component_data = {}

        # Look for a component function defined in an entry point
        eps = self._entry_points_cache.get(component_list_type)
        if eps is None:
            logging.debug("Caching entrypoints for %s" % component_list_type)
            module_eps = []
            # auto build entry points by inferring from base component_types
            mod = pkg_resources.EntryPoint("__all__",
                                           entry_point.module_name,
                                           dist=entry_point.dist)

            Mod = mod.load()
            func_eps = [
                Mod.__dict__.get(a) for a in dir(Mod)
                if isinstance(Mod.__dict__.get(a), types.FunctionType)
            ]
            for func_ep in func_eps:
                try:
                    # extract entry point based on docstring
                    name_line = func_ep.__doc__.split("\n")
                    if not name_line[0].startswith("yaml:"):
                        logger.debug("Ignoring '%s' as an entry point" %
                                     name_line)
                        continue
                    ep_name = name_line[0].split(" ")[1]
                except (AttributeError, IndexError):
                    # AttributeError by docstring not being defined as
                    # a string to have split called on it.
                    # IndexError raised by name_line not containing anything
                    # after the 'yaml:' string.
                    logger.debug("Not including func '%s' as an entry point" %
                                 func_ep.__name__)
                    continue

                module_eps.append(
                    pkg_resources.EntryPoint(
                        ep_name,
                        entry_point.module_name,
                        dist=entry_point.dist,
                        attrs=(func_ep.__name__, ),
                    ))
                logger.debug(
                    "Adding auto EP '%s=%s:%s'" %
                    (ep_name, entry_point.module_name, func_ep.__name__))

            # load from explicitly defined entry points
            module_eps.extend(
                list(
                    pkg_resources.iter_entry_points(
                        group="jenkins_jobs.{0}".format(component_list_type))))

            eps = {}
            for module_ep in module_eps:
                if module_ep.name in eps:
                    raise JenkinsJobsException(
                        "Duplicate entry point found for component type: "
                        "'{0}', '{0}',"
                        "name: '{1}'".format(component_type, name))

                eps[module_ep.name] = module_ep.load()

            # cache both sets of entry points
            self._entry_points_cache[component_list_type] = eps
            logger.debug("Cached entry point group %s = %s",
                         component_list_type, eps)

        # check for macro first
        component = self.parser_data.get(component_type, {}).get(name)
        if component:
            if name in eps and name not in self.masked_warned:
                self.masked_warned[name] = True
                logger.warning("You have a macro ('%s') defined for '%s' "
                               "component type that is masking an inbuilt "
                               "definition" % (name, component_type))

            for b in component[component_list_type]:
                # 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)
        elif name in eps:
            func = eps[name]
            func(self, xml_parent, component_data)
        else:
            raise JenkinsJobsException("Unknown entry point or macro '{0}' "
                                       "for component type: '{1}'.".format(
                                           name, component_type))
示例#8
0
def gerrit_handle_legacy_configuration(data):
    hyphenizer = re.compile("[A-Z]")

    def hyphenize(attr):
        """Convert strings like triggerOn to trigger-on.
        """
        return hyphenizer.sub(lambda x: "-%s" % x.group(0).lower(), attr)

    def convert_dict(d, old_keys):
        for old_key in old_keys:
            if old_key in d:
                new_key = hyphenize(old_key)
                logger.warn(
                    "'%s' is deprecated and will be removed after "
                    "1.0.0, please use '%s' instead", old_key, new_key)
                d[new_key] = d[old_key]
                del d[old_key]

    convert_dict(data, [
        'triggerOnPatchsetUploadedEvent',
        'triggerOnChangeAbandonedEvent',
        'triggerOnChangeMergedEvent',
        'triggerOnChangeRestoredEvent',
        'triggerOnCommentAddedEvent',
        'triggerOnDraftPublishedEvent',
        'triggerOnRefUpdatedEvent',
        'triggerApprovalCategory',
        'triggerApprovalValue',
        'overrideVotes',
        'gerritBuildSuccessfulVerifiedValue',
        'gerritBuildFailedVerifiedValue',
        'failureMessage',
        'skipVote',
    ])

    for project in data['projects']:
        convert_dict(project, [
            'projectCompareType',
            'projectPattern',
            'branchCompareType',
            'branchPattern',
        ])

    old_format_events = OrderedDict(
        (key, should_register) for key, should_register in six.iteritems(data)
        if key.startswith('trigger-on-'))
    trigger_on = data.setdefault('trigger-on', [])
    if old_format_events:
        logger.warn(
            "The events: %s; which you used is/are deprecated. "
            "Please use 'trigger-on' instead.", ', '.join(old_format_events))

    if old_format_events and trigger_on:
        raise JenkinsJobsException(
            'Both, the new format (trigger-on) and old format (trigger-on-*) '
            'gerrit events format found. Please use either the new or the old '
            'format of trigger events definition.')

    trigger_on.extend(
        event_name[len('trigger-on-'):]
        for event_name, should_register in six.iteritems(old_format_events)
        if should_register)

    for idx, event in enumerate(trigger_on):
        if event == 'comment-added-event':
            trigger_on[idx] = events = OrderedDict()
            events['comment-added-event'] = OrderedDict(
                (('approval-category', data['trigger-approval-category']),
                 ('approval-value', data['trigger-approval-value'])))
示例#9
0
def deep_format(obj, paramdict, allow_empty=False):
    """Deep format configuration.

    Apply the paramdict via str.format() to all string objects found within
    the supplied obj. Lists and dicts are traversed recursively.
    """
    # YAML serialisation was originally used to achieve this, but that places
    # limitations on the values in paramdict - the post-format result must
    # still be valid YAML (so substituting-in a string containing quotes, for
    # example, is problematic).
    if hasattr(obj, "format"):
        try:
            ret = CustomFormatter(allow_empty).format(obj, **paramdict)
        except KeyError as exc:
            missing_key = exc.args[0]
            desc = "%s parameter missing to format %s\nGiven:\n%s" % (
                missing_key,
                obj,
                pformat(paramdict),
            )
            raise JenkinsJobsException(desc)
        except Exception:
            logging.error(
                "Problem formatting with args:\nallow_empty:"
                "%s\nobj: %s\nparamdict: %s" % (allow_empty, obj, paramdict)
            )
            raise

    elif isinstance(obj, list):
        ret = type(obj)()
        for item in obj:
            ret.append(deep_format(item, paramdict, allow_empty))
    elif isinstance(obj, dict):
        ret = type(obj)()
        for item in obj:
            try:
                ret[deep_format(item, paramdict, allow_empty)] = deep_format(
                    obj[item], paramdict, allow_empty
                )
            except KeyError as exc:
                missing_key = exc.args[0]
                desc = "%s parameter missing to format %s\nGiven:\n%s" % (
                    missing_key,
                    obj,
                    pformat(paramdict),
                )
                raise JenkinsJobsException(desc)
            except Exception:
                logging.error(
                    "Problem formatting with args:\nallow_empty:"
                    "%s\nobj: %s\nparamdict: %s" % (allow_empty, obj, paramdict)
                )
                raise
    else:
        ret = obj
    if isinstance(ret, CustomLoader):
        # If we have a CustomLoader here, we've lazily-loaded a template
        # or rendered a template to a piece of YAML;
        # attempt to format it.
        ret = deep_format(
            ret.get_object_to_format(), paramdict, allow_empty=allow_empty
        )
    return ret
示例#10
0
def execute(options, config):
    logger.debug("Config: {0}".format(config))

    # check the ignore_cache setting: first from command line,
    # if not present check from ini file
    ignore_cache = False
    if options.ignore_cache:
        ignore_cache = options.ignore_cache
    elif config.has_option('jenkins', 'ignore_cache'):
        logging.warn('ignore_cache option should be moved to the [job_builder]'
                     ' section in the config file, the one specified in the '
                     '[jenkins] section will be ignored in the future')
        ignore_cache = config.getboolean('jenkins', 'ignore_cache')
    elif config.has_option('job_builder', 'ignore_cache'):
        ignore_cache = config.getboolean('job_builder', 'ignore_cache')

    # workaround for python 2.6 interpolation error
    # https://bugs.launchpad.net/openstack-ci/+bug/1259631
    try:
        user = config.get('jenkins', 'user')
    except (TypeError, configparser.NoOptionError):
        user = None
    try:
        password = config.get('jenkins', 'password')
    except (TypeError, configparser.NoOptionError):
        password = None

    plugins_info = None

    if getattr(options, 'plugins_info_path', None) is not None:
        with open(options.plugins_info_path, 'r') as yaml_file:
            plugins_info = yaml.load(yaml_file)
        if not isinstance(plugins_info, list):
            raise JenkinsJobsException("{0} must contain a Yaml list!".format(
                options.plugins_info_path))

    builder = Builder(config.get('jenkins', 'url'),
                      user,
                      password,
                      config,
                      ignore_cache=ignore_cache,
                      flush_cache=options.flush_cache,
                      plugins_list=plugins_info)

    if getattr(options, 'path', None):
        if options.path == sys.stdin:
            logger.debug("Input file is stdin")
            if options.path.isatty():
                key = 'CTRL+Z' if platform.system() == 'Windows' else 'CTRL+D'
                logger.warn(
                    "Reading configuration from STDIN. Press %s to end input.",
                    key)

        # take list of paths
        options.path = options.path.split(os.pathsep)

        do_recurse = (getattr(options, 'recursive', False)
                      or config.getboolean('job_builder', 'recursive'))

        paths = []
        for path in options.path:
            if do_recurse and os.path.isdir(path):
                paths.extend(recurse_path(path))
            else:
                paths.append(path)
        options.path = paths

    if options.command == 'delete':
        for job in options.name:
            builder.delete_job(job, options.path)
    elif options.command == 'delete-all':
        confirm('Sure you want to delete *ALL* jobs from Jenkins server?\n'
                '(including those not managed by Jenkins Job Builder)')
        logger.info("Deleting all jobs")
        builder.delete_all_jobs()
    elif options.command == 'update':
        logger.info("Updating jobs in {0} ({1})".format(
            options.path, options.names))
        jobs = builder.update_job(options.path, options.names)
        if options.delete_old:
            builder.delete_old_managed(keep=[x.name for x in jobs])
    elif options.command == 'test':
        builder.update_job(options.path,
                           options.name,
                           output=options.output_dir)
示例#11
0
def build_gerrit_triggers(xml_parent, data):
    available_simple_triggers = {
        'change-abandoned-event': 'PluginChangeAbandonedEvent',
        'change-merged-event': 'PluginChangeMergedEvent',
        'change-restored-event': 'PluginChangeRestoredEvent',
        'draft-published-event': 'PluginDraftPublishedEvent',
        'patchset-uploaded-event': 'PluginPatchsetCreatedEvent',
        'patchset-created-event': 'PluginPatchsetCreatedEvent',
        'ref-updated-event': 'PluginRefUpdatedEvent',
    }
    tag_namespace = 'com.sonyericsson.hudson.plugins.gerrit.trigger.'   \
        'hudsontrigger.events'

    trigger_on_events = XML.SubElement(xml_parent, 'triggerOnEvents')

    for event in data.get('trigger-on', []):
        if isinstance(event, six.string_types):
            tag_name = available_simple_triggers.get(event)
            if event == 'patchset-uploaded-event':
                logger.warn(
                    "'%s' is deprecated. Use 'patchset-created-event' "
                    "format instead.", event)

            if not tag_name:
                known = ', '.join(
                    available_simple_triggers.keys() +
                    ['comment-added-event', 'comment-added-contains-event'])
                msg = ("The event '%s' under 'trigger-on' is not one of the "
                       "known: %s.") % (event, known)
                raise JenkinsJobsException(msg)
            XML.SubElement(trigger_on_events,
                           '%s.%s' % (tag_namespace, tag_name))
        else:
            if 'patchset-created-event' in event.keys():
                pce = event['patchset-created-event']
                pc = XML.SubElement(
                    trigger_on_events,
                    '%s.%s' % (tag_namespace, 'PluginPatchsetCreatedEvent'))
                XML.SubElement(pc, 'excludeDrafts').text = str(
                    pce.get('exclude-drafts', False)).lower()
                XML.SubElement(pc, 'excludeTrivialRebase').text = str(
                    pce.get('exclude-trivial-rebase', False)).lower()
                XML.SubElement(pc, 'excludeNoCodeChange').text = str(
                    pce.get('exclude-no-code-change', False)).lower()

            if 'comment-added-event' in event.keys():
                comment_added_event = event['comment-added-event']
                cadded = XML.SubElement(
                    trigger_on_events,
                    '%s.%s' % (tag_namespace, 'PluginCommentAddedEvent'))
                XML.SubElement(cadded, 'verdictCategory').text = \
                    comment_added_event['approval-category']
                XML.SubElement(
                    cadded,
                    'commentAddedTriggerApprovalValue').text = \
                    str(comment_added_event['approval-value'])

            if 'comment-added-contains-event' in event.keys():
                comment_added_event = event['comment-added-contains-event']
                caddedc = XML.SubElement(
                    trigger_on_events, '%s.%s' %
                    (tag_namespace, 'PluginCommentAddedContainsEvent'))
                XML.SubElement(caddedc, 'commentAddedCommentContains').text = \
                    comment_added_event['comment-contains-value']
示例#12
0
def build_trends_publisher(plugin_name, xml_element, data):
    """Helper to create various trend publishers.
    """
    def append_thresholds(element, data, only_totals):
        """Appends the status thresholds.
        """

        for status in ["unstable", "failed"]:
            status_data = data.get(status, {})

            limits = [
                ("total-all", "TotalAll"),
                ("total-high", "TotalHigh"),
                ("total-normal", "TotalNormal"),
                ("total-low", "TotalLow"),
            ]

            if only_totals is False:
                limits.extend([
                    ("new-all", "NewAll"),
                    ("new-high", "NewHigh"),
                    ("new-normal", "NewNormal"),
                    ("new-low", "NewLow"),
                ])

            for key, tag_suffix in limits:
                tag_name = status + tag_suffix
                XML.SubElement(element,
                               tag_name).text = str(status_data.get(key, ""))

    # Tuples containing: setting name, tag name, default value
    settings = [
        ("healthy", "healthy", ""),
        ("unhealthy", "unHealthy", ""),
        ("health-threshold", "thresholdLimit", "low"),
        ("plugin-name", "pluginName", plugin_name),
        ("default-encoding", "defaultEncoding", ""),
        ("can-run-on-failed", "canRunOnFailed", False),
        ("use-stable-build-as-reference", "useStableBuildAsReference", False),
        ("use-previous-build-as-reference", "usePreviousBuildAsReference",
         False),
        ("use-delta-values", "useDeltaValues", False),
        ("thresholds", "thresholds", {}),
        ("should-detect-modules", "shouldDetectModules", False),
        ("dont-compute-new", "dontComputeNew", True),
        ("do-not-resolve-relative-paths", "doNotResolveRelativePaths", False),
        ("pattern", "pattern", ""),
    ]

    thresholds = ["low", "normal", "high"]

    for key, tag_name, default in settings:
        xml_config = XML.SubElement(xml_element, tag_name)
        config_value = data.get(key, default)

        if key == "thresholds":
            append_thresholds(xml_config, config_value,
                              data.get("dont-compute-new", True))
        elif key == "health-threshold" and config_value not in thresholds:
            raise JenkinsJobsException("health-threshold must be one of %s" %
                                       ", ".join(thresholds))
        else:
            if isinstance(default, bool):
                xml_config.text = str(config_value).lower()
            else:
                xml_config.text = str(config_value)
示例#13
0
def copyartifact(parser, xml_parent, data):
    """yaml: copyartifact

    Copy artifact from another project.  Requires the Jenkins `Copy Artifact
    plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/Copy+Artifact+Plugin>`_

    :arg str project: Project to copy from
    :arg str filter: what files to copy
    :arg str target: Target base directory for copy, blank means use workspace
    :arg bool flatten: Flatten directories (default: false)
    :arg bool optional: If the artifact is missing (for any reason) and
        optional is true, the build won't fail because of this builder
        (default: false)
    :arg str which-build: which build to get artifacts from
        (optional, default last-successful)
    :arg str build-number: specifies the build number to get when
        when specific-build is specified as which-build
    :arg str permalink: specifies the permalink to get when
        permalink is specified as which-build
    :arg bool stable: specifies to get only last stable build when
        last-successful is specified as which-build
    :arg bool fallback-to-last-successful: specifies to fallback to
        last successful build when upstream-build is specified as which-build
    :arg string param: specifies to use a build parameter to get the build when
        build-param is specified as which-build
    :arg string parameter-filters: Filter matching jobs based on these
        parameters (optional)
    :which-build values:
      * **last-successful**
      * **specific-build**
      * **last-saved**
      * **upstream-build**
      * **permalink**
      * **workspace-latest**
      * **build-param**
    :permalink values:
      * **last**
      * **last-stable**
      * **last-successful**
      * **last-failed**
      * **last-unstable**
      * **last-unsuccessful**


    Example::

      builders:
        - copyartifact:
            project: foo
            filter: *.tar.gz
            target: /home/foo
            which-build: specific-build
            build-number: 123
            optional: true
            flatten: true
            parameter-filters: PUBLISH=true
    """
    t = XML.SubElement(xml_parent, 'hudson.plugins.copyartifact.CopyArtifact')
    # Warning: this only works with copy artifact version 1.26+,
    # for copy artifact version 1.25- the 'projectName' element needs
    # to be used instead of 'project'
    XML.SubElement(t, 'project').text = data["project"]
    XML.SubElement(t, 'filter').text = data.get("filter", "")
    XML.SubElement(t, 'target').text = data.get("target", "")
    flatten = data.get("flatten", False)
    XML.SubElement(t, 'flatten').text = str(flatten).lower()
    optional = data.get('optional', False)
    XML.SubElement(t, 'optional').text = str(optional).lower()
    XML.SubElement(t, 'parameters').text = data.get("parameter-filters", "")
    select = data.get('which-build', 'last-successful')
    selectdict = {
        'last-successful': 'StatusBuildSelector',
        'specific-build': 'SpecificBuildSelector',
        'last-saved': 'SavedBuildSelector',
        'upstream-build': 'TriggeredBuildSelector',
        'permalink': 'PermalinkBuildSelector',
        'workspace-latest': 'WorkspaceSelector',
        'build-param': 'ParameterizedBuildSelector'
    }
    if select not in selectdict:
        raise JenkinsJobsException("which-build entered is not valid must be "
                                   "one of: last-successful, specific-build, "
                                   "last-saved, upstream-build, permalink, "
                                   "workspace-latest, or build-param")
    permalink = data.get('permalink', 'last')
    permalinkdict = {
        'last': 'lastBuild',
        'last-stable': 'lastStableBuild',
        'last-successful': 'lastSuccessfulBuild',
        'last-failed': 'lastFailedBuild',
        'last-unstable': 'lastUnstableBuild',
        'last-unsuccessful': 'lastUnsuccessfulBuild'
    }
    if permalink not in permalinkdict:
        raise JenkinsJobsException("permalink entered is not valid must be "
                                   "one of: last, last-stable, "
                                   "last-successful, last-failed, "
                                   "last-unstable, or last-unsuccessful")
    selector = XML.SubElement(
        t, 'selector',
        {'class': 'hudson.plugins.copyartifact.' + selectdict[select]})
    if select == 'specific-build':
        XML.SubElement(selector, 'buildNumber').text = data['build-number']
    if select == 'last-successful':
        XML.SubElement(selector,
                       'stable').text = str(data.get('stable', False)).lower()
    if select == 'upstream-build':
        XML.SubElement(selector, 'fallbackToLastSuccessful').text = str(
            data.get('fallback-to-last-successful', False)).lower()
    if select == 'permalink':
        XML.SubElement(selector, 'id').text = permalinkdict[permalink]
    if select == 'build-param':
        XML.SubElement(selector, 'parameterName').text = data['param']
示例#14
0
    def dispatch(self,
                 component_type,
                 parser,
                 xml_parent,
                 component,
                 template_data={}):
        """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
        entry points and Jenkins Job Builder :ref:`Macros <macro>`.

        :arg string component_type: the name of the component
          (e.g., `builder`)
        :arg YAMLParser parser: the global YAML Parser
        :arg Element xml_parent: the parent XML element
        :arg dict template_data: values that should be interpolated into
          the component definition

        See :py:class:`jenkins_jobs.modules.base.Base` for how to register
        components of a module.

        See the Publishers module for a simple example of how to use
        this method.
        """

        if component_type not in self.modules_by_component_type:
            raise JenkinsJobsException("Unknown component type: "
                                       "'{0}'.".format(component_type))

        component_list_type = self.modules_by_component_type[component_type] \
            .component_list_type

        if isinstance(component, dict):
            # The component is a singleton dictionary of name: dict(args)
            name, component_data = next(iter(component.items()))
            if template_data:
                # Template data contains values that should be interpolated
                # into the component definition
                allow_empty_variables = self.global_config \
                    and self.global_config.has_section('job_builder') \
                    and self.global_config.has_option(
                        'job_builder', 'allow_empty_variables') \
                    and self.global_config.getboolean(
                        'job_builder', 'allow_empty_variables')

                component_data = deep_format(component_data, template_data,
                                             allow_empty_variables)
        else:
            # The component is a simple string name, eg "run-tests"
            name = component
            component_data = {}

        # Look for a component function defined in an entry point
        eps = ModuleRegistry.entry_points_cache.get(component_list_type)
        if eps is None:
            module_eps = list(
                pkg_resources.iter_entry_points(
                    group='jenkins_jobs.{0}'.format(component_list_type)))
            eps = {}
            for module_ep in module_eps:
                if module_ep.name in eps:
                    raise JenkinsJobsException(
                        "Duplicate entry point found for component type: "
                        "'{0}', '{0}',"
                        "name: '{1}'".format(component_type, name))
                eps[module_ep.name] = module_ep

            ModuleRegistry.entry_points_cache[component_list_type] = eps
            logger.debug("Cached entry point group %s = %s",
                         component_list_type, eps)

        if name in eps:
            func = eps[name].load()
            func(parser, xml_parent, component_data)
        else:
            # Otherwise, see if it's defined as a macro
            component = parser.data.get(component_type, {}).get(name)
            if component:
                for b in component[component_list_type]:
                    # 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, parser, xml_parent, b,
                                  component_data)
            else:
                raise JenkinsJobsException(
                    "Unknown entry point or macro '{0}'"
                    " for component type: '{1}'.".format(name, component_type))
示例#15
0
def git(parser, xml_parent, data):
    """yaml: git
    Specifies the git SCM repository for this job.
    Requires the Jenkins `Git Plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin>`_

    :arg str url: URL of the git repository
    :arg str credentials-id: ID of credentials to use to connect (optional)
    :arg str refspec: refspec to fetch (default '+refs/heads/\*:refs/remotes/\
remoteName/\*')
    :arg str name: name to fetch (default 'origin')
    :arg list(str) remotes: list of remotes to set up (optional, only needed if
      multiple remotes need to be set up)

        :Remote: * **url** (`string`) - url of remote repo
                 * **refspec** (`string`) - refspec to fetch (optional)
                 * **credentials-id** - ID of credentials to use to connect
                     (optional)
    :arg list(str) branches: list of branch specifiers to build (default '**')
    :arg list(str) excluded-users: list of users to ignore revisions from
      when polling for changes. (if polling is enabled, optional)
    :arg list(str) included-regions: list of file/folders to include (optional)
    :arg list(str) excluded-regions: list of file/folders to exclude (optional)
    :arg str local-branch: Checkout/merge to local branch (optional)
    :arg dict merge:
        :merge:
            * **remote** (`string`) - name of repo that contains branch to
                merge to (default 'origin')
            * **branch** (`string`) - name of the branch to merge to
    :arg str basedir: location relative to the workspace root to clone to
             (default: workspace)
    :arg bool skip-tag: Skip tagging (default false)
    :arg bool shallow-clone: Perform shallow clone (default false)
    :arg bool prune: Prune remote branches (default false)
    :arg bool clean: Clean after checkout (default false)
    :arg bool fastpoll: Use fast remote polling (default false)
    :arg bool disable-submodules: Disable submodules (default false)
    :arg bool recursive-submodules: Recursively update submodules (default
      false)
    :arg bool use-author: Use author rather than committer in Jenkin's build
      changeset (default false)
    :arg str git-tool: The name of the Git installation to use (default
      'Default')
    :arg str reference-repo: Path of the reference repo to use during clone
      (optional)
    :arg str scm-name: The unique scm name for this Git SCM (optional)
    :arg bool wipe-workspace: Wipe out workspace before build (default true)
    :arg bool ignore-notify: Ignore notifyCommit URL accesses (default false)
    :arg str browser: what repository browser to use (default '(Auto)')
    :arg str browser-url: url for the repository browser (required if browser
      is not '(Auto)', no default)
    :arg str browser-version: version of the repository browser (GitLab only,
      default '0.0')
    :arg str project-name: project name in Gitblit and ViewGit repobrowser
      (optional)
    :arg str choosing-strategy: Jenkins class for selecting what to build
      (default 'default')
    :arg str git-config-name: Configure name for Git clone (optional)
    :arg str git-config-email: Configure email for Git clone (optional)
    :arg str timeout: Timeout for git commands in minutes (optional)

    :browser values:
        :auto:
        :bitbucketweb:
        :cgit:
        :fisheye:
        :gitblit:
        :githubweb:
        :gitlab:
        :gitoriousweb:
        :gitweb:
        :redmineweb:
        :stash:
        :viewgit:

    :choosing-strategy values:
        :default:
        :inverse:
        :gerrit:

    Example:

    .. literalinclude:: /../../tests/scm/fixtures/git001.yaml
    """

    # XXX somebody should write the docs for those with option name =
    # None so we have a sensible name/key for it.
    mapping = [
        # option, xml name, default value (text), attributes (hard coded)
        ("disable-submodules", 'disableSubmodules', False),
        ("recursive-submodules", 'recursiveSubmodules', False),
        (None, 'doGenerateSubmoduleConfigurations', False),
        ("use-author", 'authorOrCommitter', False),
        ("clean", 'clean', False),
        ("wipe-workspace", 'wipeOutWorkspace', True),
        ("prune", 'pruneBranches', False),
        ("fastpoll", 'remotePoll', False),
        ("git-tool", 'gitTool', "Default"),
        (None, 'submoduleCfg', '', {
            'class': 'list'
        }),
        ('basedir', 'relativeTargetDir', ''),
        ('reference-repo', 'reference', ''),
        ("git-config-name", 'gitConfigName', ''),
        ("git-config-email", 'gitConfigEmail', ''),
        ('skip-tag', 'skipTag', False),
        ('scm-name', 'scmName', ''),
        ("shallow-clone", "useShallowClone", False),
        ("ignore-notify", "ignoreNotifyCommit", False),
    ]

    choosing_strategies = {
        'default':
        'hudson.plugins.git.util.DefaultBuildChooser',
        'gerrit': ('com.sonyericsson.hudson.plugins.'
                   'gerrit.trigger.hudsontrigger.GerritTriggerBuildChooser'),
        'inverse':
        'hudson.plugins.git.util.InverseBuildChooser',
    }

    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.plugins.git.GitSCM'})
    XML.SubElement(scm, 'configVersion').text = '2'
    user = XML.SubElement(scm, 'userRemoteConfigs')
    if 'remotes' not in data:
        data['remotes'] = [{data.get('name', 'origin'): data.copy()}]
    for remoteData in data['remotes']:
        huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
        remoteName = next(iter(remoteData.keys()))
        XML.SubElement(huser, 'name').text = remoteName
        remoteParams = next(iter(remoteData.values()))
        if 'refspec' in remoteParams:
            refspec = remoteParams['refspec']
        else:
            refspec = '+refs/heads/*:refs/remotes/' + remoteName + '/*'
        XML.SubElement(huser, 'refspec').text = refspec
        if 'url' in remoteParams:
            remoteURL = remoteParams['url']
        else:
            raise JenkinsJobsException('Must specify a url for git remote \"' +
                                       remoteName + '"')
        XML.SubElement(huser, 'url').text = remoteURL
        if 'credentials-id' in remoteParams:
            credentialsId = remoteParams['credentials-id']
            XML.SubElement(huser, 'credentialsId').text = credentialsId
    xml_branches = XML.SubElement(scm, 'branches')
    branches = data.get('branches', ['**'])
    for branch in branches:
        bspec = XML.SubElement(xml_branches, 'hudson.plugins.git.BranchSpec')
        XML.SubElement(bspec, 'name').text = branch
    excluded_users = '\n'.join(data.get('excluded-users', []))
    XML.SubElement(scm, 'excludedUsers').text = excluded_users
    if 'included-regions' in data:
        include_string = '\n'.join(data['included-regions'])
        XML.SubElement(scm, 'includedRegions').text = include_string
    if 'excluded-regions' in data:
        exclude_string = '\n'.join(data['excluded-regions'])
        XML.SubElement(scm, 'excludedRegions').text = exclude_string
    if 'merge' in data:
        merge = data['merge']
        name = merge.get('remote', 'origin')
        branch = merge['branch']
        urc = XML.SubElement(scm, 'userMergeOptions')
        XML.SubElement(urc, 'mergeRemote').text = name
        XML.SubElement(urc, 'mergeTarget').text = branch

    try:
        choosing_strategy = choosing_strategies[data.get(
            'choosing-strategy', 'default')]
    except KeyError:
        raise ValueError('Invalid choosing-strategy %r' %
                         data.get('choosing-strategy'))
    XML.SubElement(scm, 'buildChooser', {'class': choosing_strategy})

    for elem in mapping:
        (optname, xmlname, val) = elem[:3]
        attrs = {}
        if len(elem) >= 4:
            attrs = elem[3]
        xe = XML.SubElement(scm, xmlname, attrs)
        if optname and optname in data:
            val = data[optname]
        if type(val) == bool:
            xe.text = str(val).lower()
        else:
            xe.text = val

    if 'local-branch' in data:
        XML.SubElement(scm, 'localBranch').text = data['local-branch']

    if 'timeout' in data:
        ext = XML.SubElement(scm, 'extensions')
        co = XML.SubElement(
            ext, 'hudson.plugins.git.extensions.impl.'
            'CheckoutOption')
        XML.SubElement(co, 'timeout').text = str(data['timeout'])

    browser = data.get('browser', 'auto')
    browserdict = {
        'auto': 'auto',
        'bitbucketweb': 'BitbucketWeb',
        'cgit': 'CGit',
        'fisheye': 'FisheyeGitRepositoryBrowser',
        'gitblit': 'GitBlitRepositoryBrowser',
        'githubweb': 'GithubWeb',
        'gitlab': 'GitLab',
        'gitoriousweb': 'GitoriousWeb',
        'gitweb': 'GitWeb',
        'redmineweb': 'RedmineWeb',
        'stash': 'Stash',
        'viewgit': 'ViewGitWeb'
    }
    if browser not in browserdict:
        valid = sorted(browserdict.keys())
        raise JenkinsJobsException("Browser entered is not valid must be one "
                                   "of: %s or %s." %
                                   (", ".join(valid[:-1]), valid[-1]))
    if browser != 'auto':
        bc = XML.SubElement(
            scm, 'browser',
            {'class': 'hudson.plugins.git.browser.' + browserdict[browser]})
        XML.SubElement(bc, 'url').text = data['browser-url']
        if browser in ['gitblit', 'viewgit']:
            XML.SubElement(bc, 'projectName').text = str(
                data.get('project-name', ''))
        if browser == 'gitlab':
            XML.SubElement(bc, 'version').text = str(
                data.get('browser-version', '0.0'))
示例#16
0
def pollurl(parser, xml_parent, data):
    """yaml: pollurl
    Trigger when the HTTP response from a URL changes.
    Requires the Jenkins `URLTrigger Plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/URLTrigger+Plugin>`_

    :arg string cron: cron syntax of when to run (default '')
    :arg string polling-node: Restrict where the polling should run.
                              (optional)
    :arg list urls: List of URLs to monitor

      :URL: * **url** (`str`) -- URL to monitor for changes (required)
            * **proxy** (`bool`) -- Activate the Jenkins proxy (default false)
            * **timeout** (`int`) -- Connect/read timeout in seconds
              (default 300)
            * **username** (`string`) -- User name for basic authentication
              (optional)
            * **password** (`string`) -- Password for basic authentication
              (optional)
            * **check-status** (`int`) -- Check for a specific HTTP status
              code (optional)
            * **check-etag** (`bool`) -- Check the HTTP ETag for changes
              (default false)
            * **check-date** (`bool`) -- Check the last modification date of
              the URL (default false)
            * **check-content** (`list`) -- List of content type changes to
              monitor

              :Content Type: * **simple** (`bool`) -- Trigger on any change to
                               the content of the URL (default false)
                             * **json** (`list`) -- Trigger on any change to
                               the listed JSON paths
                             * **text** (`list`) -- Trigger on any change to
                               the listed regular expressions
                             * **xml** (`list`) -- Trigger on any change to
                               the listed XPath expressions

    Example:

    .. literalinclude:: /../../tests/triggers/fixtures/pollurl001.yaml
    """

    valid_content_types = {
        'simple': ['Simple', '', '', []],
        'json': ['JSON', 'jsonPaths', 'jsonPath', None],
        'text': ['TEXT', 'regExElements', 'regEx', None],
        'xml': ['XML', 'xPaths', 'xPath', None]
    }
    urltrig = XML.SubElement(xml_parent,
                             'org.jenkinsci.plugins.urltrigger.URLTrigger')
    node = data.get('polling-node')
    XML.SubElement(urltrig, 'spec').text = data.get('cron', '')
    XML.SubElement(urltrig, 'labelRestriction').text = str(bool(node)).lower()
    if node:
        XML.SubElement(urltrig, 'triggerLabel').text = node
    entries = XML.SubElement(urltrig, 'entries')
    urls = data.get('urls', [])
    if not urls:
        raise JenkinsJobsException('At least one url must be provided')
    for url in urls:
        entry = XML.SubElement(entries,
                               'org.jenkinsci.plugins.urltrigger.'
                               'URLTriggerEntry')
        XML.SubElement(entry, 'url').text = url['url']
        XML.SubElement(entry, 'proxyActivated').text = \
            str(url.get('proxy', False)).lower()
        if 'username' in url:
            XML.SubElement(entry, 'username').text = url['username']
        if 'password' in url:
            XML.SubElement(entry, 'password').text = url['password']
        if 'check-status' in url:
            XML.SubElement(entry, 'checkStatus').text = 'true'
            XML.SubElement(entry, 'statusCode').text = \
                str(url.get('check-status'))
        else:
            XML.SubElement(entry, 'checkStatus').text = 'false'
            XML.SubElement(entry, 'statusCode').text = '200'
        XML.SubElement(entry, 'timeout').text = \
            str(url.get('timeout', 300))
        XML.SubElement(entry, 'checkETag').text = \
            str(url.get('check-etag', False)).lower()
        XML.SubElement(entry, 'checkLastModificationDate').text = \
            str(url.get('check-date', False)).lower()
        check_content = url.get('check-content', [])
        XML.SubElement(entry, 'inspectingContent').text = \
            str(bool(check_content)).lower()
        content_types = XML.SubElement(entry, 'contentTypes')
        for entry in check_content:
            type_name = entry.keys()[0]
            if type_name not in valid_content_types:
                raise JenkinsJobsException('check-content must be one of : %s'
                                           % ', '.join(valid_content_types.
                                                       keys()))

            content_type = valid_content_types.get(type_name)
            if entry[type_name]:
                sub_entries = content_type[3]
                if sub_entries is None:
                    sub_entries = entry[type_name]
                build_pollurl_content_type(content_types,
                                           sub_entries,
                                           *content_type[0:3])
示例#17
0
def svn(parser, xml_parent, data):
    """yaml: svn
    Specifies the svn SCM repository for this job.

    :arg str url: URL of the svn repository
    :arg str basedir: location relative to the workspace root to checkout to
      (default '.')
    :arg str credentials-id: optional argument to specify the ID of credentials
      to use
    :arg str workspaceupdater: optional argument to specify
      how to update the workspace (default wipeworkspace)
    :arg list(str) excluded-users: list of users to ignore revisions from
      when polling for changes (if polling is enabled; parameter is optional)
    :arg list(str) included-regions: list of file/folders to include
      (optional)
    :arg list(str) excluded-regions: list of file/folders to exclude (optional)
    :arg list(str) excluded-commit-messages: list of commit messages to exclude
      (optional)
    :arg str exclusion-revprop-name: revision svn-property to ignore (optional)
    :arg bool ignore-property-changes-on-directories: ignore svn-property only
      changes of directories (default false)
    :arg bool filter-changelog: If set Jenkins will apply the same inclusion
      and exclusion patterns for displaying changelog entries as it does for
      polling for changes (default false)
    :arg list repos: list of repositories to checkout (optional)

      :Repo: * **url** (`str`) -- URL for the repository
             * **basedir** (`str`) -- Location relative to the workspace
                                      root to checkout to (default '.')
             * **credentials-id** - optional ID of credentials to use

    :workspaceupdater values:
             :wipeworkspace: - deletes the workspace before checking out
             :revertupdate:  - do an svn revert then an svn update
             :emulateclean:  - delete unversioned/ignored files then update
             :update:        - do an svn update as much as possible

    Multiple repos example:

    .. literalinclude:: /../../tests/scm/fixtures/svn-multiple-repos-001.yaml

    Advanced commit filtering example:

    .. literalinclude:: /../../tests/scm/fixtures/svn-regions-001.yaml
    """
    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.scm.SubversionSCM'})
    locations = XML.SubElement(scm, 'locations')
    if 'repos' in data:
        repos = data['repos']
        for repo in repos:
            module = XML.SubElement(
                locations, 'hudson.scm.SubversionSCM_-ModuleLocation')
            XML.SubElement(module, 'remote').text = repo['url']
            XML.SubElement(module, 'local').text = repo.get('basedir', '.')
            if 'credentials-id' in repo:
                XML.SubElement(module,
                               'credentialsId').text = repo['credentials-id']
    elif 'url' in data:
        module = XML.SubElement(locations,
                                'hudson.scm.SubversionSCM_-ModuleLocation')
        XML.SubElement(module, 'remote').text = data['url']
        XML.SubElement(module, 'local').text = data.get('basedir', '.')
        if 'credentials-id' in data:
            XML.SubElement(module,
                           'credentialsId').text = data['credentials-id']
    else:
        raise JenkinsJobsException("A top level url or repos list must exist")
    updater = data.get('workspaceupdater', 'wipeworkspace')
    if updater == 'wipeworkspace':
        updaterclass = 'CheckoutUpdater'
    elif updater == 'revertupdate':
        updaterclass = 'UpdateWithRevertUpdater'
    elif updater == 'emulateclean':
        updaterclass = 'UpdateWithCleanUpdater'
    elif updater == 'update':
        updaterclass = 'UpdateUpdater'
    XML.SubElement(scm, 'workspaceUpdater',
                   {'class': 'hudson.scm.subversion.' + updaterclass})

    mapping = [
        # option, xml name, default value
        ("excluded-regions", 'excludedRegions', []),
        ("included-regions", 'includedRegions', []),
        ("excluded-users", 'excludedUsers', []),
        ("exclusion-revprop-name", 'excludedRevprop', ''),
        ("excluded-commit-messages", 'excludedCommitMessages', []),
        ("ignore-property-changes-on-directories", 'ignoreDirPropChanges',
         False),
        ("filter-changelog", 'filterChangelog', False),
    ]

    for optname, xmlname, defvalue in mapping:
        if isinstance(defvalue, list):
            val = '\n'.join(data.get(optname, defvalue))
        else:
            val = data.get(optname, defvalue)
        # Skip adding xml entry if default is empty and no value given
        if not val and (defvalue in ['', []]):
            continue

        xe = XML.SubElement(scm, xmlname)
        if isinstance(defvalue, bool):
            xe.text = str(val).lower()
        else:
            xe.text = str(val)
    def _setup(self):
        config = self.config_parser

        logger.debug("Config: {0}".format(config))

        # check the ignore_cache setting
        if config.has_option(self.section, 'ignore_cache'):
            logging.warning("ignore_cache option should be moved to the "
                            "[job_builder] section in the config file, the "
                            "one specified in the [jenkins] section will be "
                            "ignored in the future")
            self.ignore_cache = config.getboolean(self.section, 'ignore_cache')
        elif config.has_option('job_builder', 'ignore_cache'):
            self.ignore_cache = config.getboolean('job_builder',
                                                  'ignore_cache')

        # check the flush_cache setting
        if config.has_option('job_builder', 'flush_cache'):
            self.flush_cache = config.getboolean('job_builder', 'flush_cache')

        # Jenkins supports access as an anonymous user, which can be used to
        # ensure read-only behaviour when querying the version of plugins
        # installed for test mode to generate XML output matching what will be
        # uploaded. To enable must pass 'None' as the value for user and
        # password to python-jenkins
        #
        # catching 'TypeError' is a workaround for python 2.6 interpolation
        # error
        # https://bugs.launchpad.net/openstack-ci/+bug/1259631
        try:
            self.user = config.get(self.section, 'user')
        except (TypeError, configparser.NoOptionError):
            pass

        try:
            if config.has_option(self.section, 'password'):
                self.password = config.get(self.section, 'password')
            else:
                self.password = getpass("Enter Jenkins config Password:"******"timeout=jenkins_jobs.builder._DEFAULT_TIMEOUT" or not set timeout at
        # all.
        try:
            self.timeout = config.getfloat(self.section, 'timeout')
        except (ValueError):
            raise JenkinsJobsException("Jenkins timeout config is invalid")
        except (TypeError, configparser.NoOptionError):
            pass

        if (config.has_option(self.section, 'query_plugins_info')
                and not config.getboolean(self.section, "query_plugins_info")):
            logger.debug("Skipping plugin info retrieval")
            self.plugins_info = []

        self.recursive = config.getboolean('job_builder', 'recursive')
        self.excludes = config.get('job_builder', 'exclude').split(os.pathsep)

        # The way we want to do things moving forward:
        self.jenkins['url'] = config.get(self.section, 'url')
        self.jenkins['user'] = self.user
        self.jenkins['password'] = self.password
        self.jenkins['timeout'] = self.timeout

        self.builder['ignore_cache'] = self.ignore_cache
        self.builder['flush_cache'] = self.flush_cache
        self.builder['plugins_info'] = self.plugins_info

        # keep descriptions ? (used by yamlparser)
        keep_desc = False
        if (config and config.has_section('job_builder')
                and config.has_option('job_builder', 'keep_descriptions')):
            keep_desc = config.getboolean('job_builder', 'keep_descriptions')
        self.yamlparser['keep_descriptions'] = keep_desc

        # figure out the include path (used by yamlparser)
        path = ["."]
        if (config and config.has_section('job_builder')
                and config.has_option('job_builder', 'include_path')):
            path = config.get('job_builder', 'include_path').split(':')
        self.yamlparser['include_path'] = path

        # allow duplicates?
        allow_duplicates = False
        if config and config.has_option('job_builder', 'allow_duplicates'):
            allow_duplicates = config.getboolean('job_builder',
                                                 'allow_duplicates')
        self.yamlparser['allow_duplicates'] = allow_duplicates

        # allow empty variables?
        self.yamlparser['allow_empty_variables'] = (
            self.allow_empty_variables
            or config and config.has_section('job_builder')
            and config.has_option('job_builder', 'allow_empty_variables')
            and config.getboolean('job_builder', 'allow_empty_variables'))
    def git(self, xml_parent, data):
        logger = logging.getLogger("%s:pipeline-git" % __name__)
        scm = XML.SubElement(xml_parent, 'scm',
                             {'class': 'hudson.plugins.git.GitSCM'})
        scm.attrib['plugin'] = 'git'
        XML.SubElement(scm, 'configVersion').text = '2'

        user = XML.SubElement(scm, 'userRemoteConfigs')

        if 'remotes' not in data:
            data['remotes'] = [{data.get('name', 'origin'): data.copy()}]

        for remoteData in data['remotes']:
            huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
            remoteName = next(iter(remoteData.keys()))
            #XML.SubElement(huser, 'name').text = remoteName
            remoteParams = next(iter(remoteData.values()))

            if 'url' in remoteParams:
                remoteURL = remoteParams['url']
            else:
                raise JenkinsJobsException(
                    'Must specify a url for git remote \"' + remoteName + '"')
            XML.SubElement(huser, 'url').text = remoteURL
            if 'credentials-id' in remoteParams:
                credentialsId = remoteParams['credentials-id']
                XML.SubElement(huser, 'credentialsId').text = credentialsId

        xml_branches = XML.SubElement(scm, 'branches')
        branches = data.get('branches', ['**'])

        for branch in branches:
            bspec = XML.SubElement(xml_branches,
                                   'hudson.plugins.git.BranchSpec')
            XML.SubElement(bspec, 'name').text = branch

        # add addition elements
        #<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
        #<submoduleCfg class="list"/>

        mappingOld = [
            # option, xml name, default value (text), attributes (hard coded)
            (None, 'doGenerateSubmoduleConfigurations', False),
            (None, 'submoduleCfg', '', {
                'class': 'list'
            }),
        ]

        mapping = [
            # option, xml name, default value (text), attributes (hard coded)
            ("ignore-notify", "ignoreNotifyCommit", False),
            ("shallow-clone", "useShallowClone", False),
        ]

        # first adding the deprecated options
        for elem in mappingOld:
            (optname, xmlname, val) = elem[:3]
            attrs = {}
            attrs = {}
            if len(elem) >= 4:
                attrs = elem[3]
            xe = XML.SubElement(scm, xmlname, attrs)
            if optname and optname in data:
                val = data[optname]
            if type(val) == bool:
                xe.text = str(val).lower()
            else:
                xe.text = val

        exts_node = XML.SubElement(scm, 'extensions')
        for elem in mapping:
            (optname, xmlname, val) = elem[:3]
            attrs = {}
            if len(elem) >= 4:
                attrs = elem[3]
            xe = XML.SubElement(exts_node, xmlname, attrs)
            if optname and optname in data:
                val = data[optname]
            if type(val) == bool:
                xe.text = str(val).lower()
            else:
                xe.text = val
示例#20
0
def git(self, xml_parent, data):
    """yaml: git
    Specifies the git SCM repository for this job.
    Requires the Jenkins `Git Plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin>`_

    :arg str url: URL of the git repository
    :arg str refspec: refspec to fetch
    :arg str name: name to fetch
    :arg list(str) branches: list of branch specifiers to build
    :arg list(str) excluded-users: list of users to ignore revisions from
      when polling for changes. (if polling is enabled)
    :arg list(str) included-regions: list of file/folders to include
    :arg list(str) excluded-regions: list of file/folders to exclude
    :arg dict merge:
        :merge:
            * **remote** (`string`) - name of repo that contains branch to
                merge to (default 'origin')
            * **branch** (`string`) - name of the branch to merge to
    :arg str basedir: location relative to the workspace root to clone to
             (default: workspace)
    :arg bool skip-tag: Skip tagging
    :arg bool shallow-clone: Perform shallow clone
    :arg bool prune: Prune remote branches
    :arg bool clean: Clean after checkout
    :arg bool fastpoll: Use fast remote polling
    :arg bool disable-submodules: Disable submodules
    :arg bool recursive-submodules: Recursively update submodules
    :arg bool use-author: Use author rather than committer in Jenkin's build
      changeset
    :arg str git-tool: The name of the Git installation to use
    :arg str reference-repo: Path of the reference repo to use during clone
    :arg str scm-name: The unique scm name for this Git SCM
    :arg bool wipe-workspace: Wipe out workspace before build
    :arg str browser: what repository browser to use (default '(Auto)')
    :arg str browser-url: url for the repository browser
    :arg str browser-version: version of the repository browser (GitLab)
    :arg str project-name: project name in Gitblit and ViewGit repobrowser
    :arg str choosing-strategy: Jenkins class for selecting what to build
    :arg str git-config-name: Configure name for Git clone
    :arg str git-config-email: Configure email for Git clone

    :browser values:
        :githubweb:
        :fisheye:
        :bitbucketweb:
        :gitblit:
        :gitlab:
        :gitoriousweb:
        :gitweb:
        :redmineweb:
        :viewgit:

    :choosing-strategy values:
        :default:
        :inverse:
        :gerrit:

    Example::

      scm:
        - git:
          url: https://example.com/project.git
          branches:
            - master
            - stable
          browser: githubweb
          browser-url: http://github.com/foo/example.git
    """

    # XXX somebody should write the docs for those with option name =
    # None so we have a sensible name/key for it.
    mapping = [
        # option, xml name, default value (text), attributes (hard coded)
        ("disable-submodules", 'disableSubmodules', False),
        ("recursive-submodules", 'recursiveSubmodules', False),
        (None, 'doGenerateSubmoduleConfigurations', False),
        ("use-author", 'authorOrCommitter', False),
        ("clean", 'clean', False),
        ("wipe-workspace", 'wipeOutWorkspace', True),
        ("prune", 'pruneBranches', False),
        ("fastpoll", 'remotePoll', False),
        ("git-tool", 'gitTool', "Default"),
        (None, 'submoduleCfg', '', {
            'class': 'list'
        }),
        ('basedir', 'relativeTargetDir', ''),
        ('reference-repo', 'reference', ''),
        ("git-config-name", 'gitConfigName', ''),
        ("git-config-email", 'gitConfigEmail', ''),
        ('skip-tag', 'skipTag', False),
        ('scm-name', 'scmName', ''),
        ("shallow-clone", "useShallowClone", False),
    ]

    choosing_strategies = {
        'default':
        'hudson.plugins.git.util.DefaultBuildChooser',
        'gerrit': ('com.sonyericsson.hudson.plugins.'
                   'gerrit.trigger.hudsontrigger.GerritTriggerBuildChooser'),
        'inverse':
        'hudson.plugins.git.util.InverseBuildChooser',
    }

    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.plugins.git.GitSCM'})
    XML.SubElement(scm, 'configVersion').text = '2'
    user = XML.SubElement(scm, 'userRemoteConfigs')
    huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
    XML.SubElement(huser, 'name').text = data.get('name', 'origin')
    if 'refspec' in data:
        refspec = data['refspec']
    else:
        refspec = '+refs/heads/*:refs/remotes/origin/*'
    XML.SubElement(huser, 'refspec').text = refspec
    XML.SubElement(huser, 'url').text = data['url']
    xml_branches = XML.SubElement(scm, 'branches')
    branches = data.get('branches', ['**'])
    for branch in branches:
        bspec = XML.SubElement(xml_branches, 'hudson.plugins.git.BranchSpec')
        XML.SubElement(bspec, 'name').text = branch
    excluded_users = '\n'.join(data.get('excluded-users', []))
    XML.SubElement(scm, 'excludedUsers').text = excluded_users
    if 'included-regions' in data:
        include_string = '\n'.join(data['included-regions'])
        XML.SubElement(scm, 'includedRegions').text = include_string
    if 'excluded-regions' in data:
        exclude_string = '\n'.join(data['excluded-regions'])
        XML.SubElement(scm, 'excludedRegions').text = exclude_string
    if 'merge' in data:
        merge = data['merge']
        name = merge.get('remote', 'origin')
        branch = merge['branch']
        urc = XML.SubElement(scm, 'userMergeOptions')
        XML.SubElement(urc, 'mergeRemote').text = name
        XML.SubElement(urc, 'mergeTarget').text = branch

    try:
        choosing_strategy = choosing_strategies[data.get(
            'choosing-strategy', 'default')]
    except KeyError:
        raise ValueError('Invalid choosing-strategy %r' %
                         data.get('choosing-strategy'))
    XML.SubElement(scm, 'buildChooser', {'class': choosing_strategy})

    for elem in mapping:
        (optname, xmlname, val) = elem[:3]
        attrs = {}
        if len(elem) >= 4:
            attrs = elem[3]
        xe = XML.SubElement(scm, xmlname, attrs)
        if optname and optname in data:
            val = data[optname]
        if type(val) == bool:
            xe.text = str(val).lower()
        else:
            xe.text = val
    browser = data.get('browser', 'auto')
    browserdict = {
        'githubweb': 'GithubWeb',
        'fisheye': 'FisheyeGitRepositoryBrowser',
        'bitbucketweb': 'BitbucketWeb',
        'cgit': 'CGit',
        'gitblit': 'GitBlitRepositoryBrowser',
        'gitlab': 'GitLab',
        'gitoriousweb': 'GitoriousWeb',
        'gitweb': 'GitWeb',
        'redmineweb': 'RedmineWeb',
        'viewgit': 'ViewGitWeb',
        'auto': 'auto'
    }
    if browser not in browserdict:
        raise JenkinsJobsException("Browser entered is not valid must be one "
                                   "of: githubweb, fisheye, bitbucketweb, "
                                   "cgit, gitblit, gitlab, gitoriousweb, "
                                   "gitweb, redmineweb, viewgit, or auto")
    if browser != 'auto':
        bc = XML.SubElement(
            scm, 'browser',
            {'class': 'hudson.plugins.git.browser.' + browserdict[browser]})
        XML.SubElement(bc, 'url').text = data['browser-url']
        if browser in ['gitblit', 'viewgit']:
            XML.SubElement(bc, 'projectName').text = str(
                data.get('project-name', ''))
        if browser == 'gitlab':
            XML.SubElement(bc, 'version').text = str(
                data.get('browser-version', '0.0'))
    def _setup(self):
        config = self.config_parser

        logger.debug("Config: {0}".format(config))

        # check the ignore_cache setting
        ignore_cache = False
        if config.has_option(self._section, "ignore_cache"):
            logger.warning(
                "ignore_cache option should be moved to the "
                "[job_builder] section in the config file, the "
                "one specified in the [jenkins] section will be "
                "ignored in the future"
            )
            ignore_cache = config.getboolean(self._section, "ignore_cache")
        elif config.has_option("job_builder", "ignore_cache"):
            ignore_cache = config.getboolean("job_builder", "ignore_cache")
        self.builder["ignore_cache"] = ignore_cache

        # check the flush_cache setting
        flush_cache = False
        if config.has_option("job_builder", "flush_cache"):
            flush_cache = config.getboolean("job_builder", "flush_cache")
        self.builder["flush_cache"] = flush_cache

        # check the print_job_urls setting
        if config.has_option("job_builder", "print_job_urls"):
            self.print_job_urls = config.getboolean("job_builder", "print_job_urls")

        # Jenkins supports access as an anonymous user, which can be used to
        # ensure read-only behaviour when querying the version of plugins
        # installed for test mode to generate XML output matching what will be
        # uploaded. To enable must pass 'None' as the value for user and
        # password to python-jenkins
        #
        # catching 'TypeError' is a workaround for python 2.6 interpolation
        # error
        # https://bugs.launchpad.net/openstack-ci/+bug/1259631

        try:
            user = config.get(self._section, "user")
        except (TypeError, configparser.NoOptionError):
            user = None
        self.jenkins["user"] = user

        try:
            password = config.get(self._section, "password")
        except (TypeError, configparser.NoOptionError):
            password = None
        self.jenkins["password"] = password

        # None -- no timeout, blocking mode; same as setblocking(True)
        # 0.0 -- non-blocking mode; same as setblocking(False) <--- default
        # > 0 -- timeout mode; operations time out after timeout seconds
        # < 0 -- illegal; raises an exception
        # to retain the default must use
        # "timeout=jenkins_jobs.builder._DEFAULT_TIMEOUT" or not set timeout at
        # all.
        try:
            timeout = config.getfloat(self._section, "timeout")
        except (ValueError):
            raise JenkinsJobsException("Jenkins timeout config is invalid")
        except (TypeError, configparser.NoOptionError):
            timeout = builder._DEFAULT_TIMEOUT
        self.jenkins["timeout"] = timeout

        plugins_info = None
        if config.has_option(
            self._section, "query_plugins_info"
        ) and not config.getboolean(self._section, "query_plugins_info"):
            logger.debug("Skipping plugin info retrieval")
            plugins_info = []
        self.builder["plugins_info"] = plugins_info

        self.recursive = config.getboolean("job_builder", "recursive")
        self.excludes = config.get("job_builder", "exclude").split(os.pathsep)

        # The way we want to do things moving forward:
        self.jenkins["url"] = config.get(self._section, "url")
        self.builder["print_job_urls"] = self.print_job_urls

        # keep descriptions ? (used by yamlparser)
        keep_desc = False
        if (
            config
            and config.has_section("job_builder")
            and config.has_option("job_builder", "keep_descriptions")
        ):
            keep_desc = config.getboolean("job_builder", "keep_descriptions")
        self.yamlparser["keep_descriptions"] = keep_desc

        # figure out the include path (used by yamlparser)
        path = ["."]
        if (
            config
            and config.has_section("job_builder")
            and config.has_option("job_builder", "include_path")
        ):
            path = config.get("job_builder", "include_path").split(":")
        self.yamlparser["include_path"] = path

        # allow duplicates?
        allow_duplicates = False
        if config and config.has_option("job_builder", "allow_duplicates"):
            allow_duplicates = config.getboolean("job_builder", "allow_duplicates")
        self.yamlparser["allow_duplicates"] = allow_duplicates

        # allow empty variables?
        self.yamlparser["allow_empty_variables"] = (
            config
            and config.has_section("job_builder")
            and config.has_option("job_builder", "allow_empty_variables")
            and config.getboolean("job_builder", "allow_empty_variables")
        )

        # retain anchors across files?
        retain_anchors = False
        if config and config.has_option("job_builder", "retain_anchors"):
            retain_anchors = config.getboolean("job_builder", "retain_anchors")
        self.yamlparser["retain_anchors"] = retain_anchors

        update = None
        if (
            config
            and config.has_section("job_builder")
            and config.has_option("job_builder", "update")
        ):
            update = config.get("job_builder", "update")
        self.builder["update"] = update
示例#22
0
def cvs(parser, xml_parent, data):
    """yaml: cvs
    Specifies the CVS SCM repository for this job.
    Requires the Jenkins :jenkins-wiki:`CVS Plugin <CVS+Plugin>`.

    :arg list repos: List of CVS repositories. (required)

        :Repos:
            * **root** (`str`) -- The CVS connection string Jenkins uses to
              connect to the server. The format is :protocol:user@host:path
              (required)
            * **locations** (`list`) -- List of locations. (required)

                :Locations:
                    * **type** (`str`) -- Type of location.

                        :supported values:
                            * **HEAD** - (default)
                            * **BRANCH**
                            * **TAG**
                    * **name** (`str`) -- Name of location. Only valid in case
                      of 'BRANCH' or 'TAG' location type. (default '')
                    * **use-head** (`bool`) -- Use Head if not found. Only
                      valid in case of 'BRANCH' or 'TAG' location type.
                      (default false)
                    * **modules** (`list`) -- List of modules. (required)

                        :Modules:
                            * **remote** -- The name of the module in the
                              repository at CVSROOT. (required)
                            * **local-name** --  The name to be applied to
                              this module in the local workspace. If blank,
                              the remote module name will be used.
                              (default '')
            * **excluded-regions** (`list str`) -- Patterns for excluding
              regions. (optional)
            * **compression-level** (`int`) -- Compression level. Must be a
              number between -1 and 9 inclusive. Choose -1 for System Default.
              (default -1)
    :arg bool use-update: If true, Jenkins will use 'cvs update' whenever
      possible for builds. This makes a build faster. But this also causes the
      artifacts from the previous build to remain in the file system when a
      new build starts, making it not a true clean build. (default true)
    :arg bool prune-empty: Remove empty directories after checkout using the
      CVS '-P' option. (default true)
    :arg bool skip-changelog: Prevent the changelog being generated after
      checkout has completed. (default false)
    :arg bool show-all-output: Instructs CVS to show all logging output. CVS
      normally runs in quiet mode but this option disables that.
      (default false)
    :arg bool clean-checkout: Perform clean checkout on failed update.
      (default false)
    :arg bool clean-copy: Force clean copy for locally modified files.
      (default false)

    Example

    .. literalinclude:: /../../tests/scm/fixtures/cvs001.yaml
       :language: yaml
    .. literalinclude:: /../../tests/scm/fixtures/cvs002.yaml
       :language: yaml
    """
    prefix = 'hudson.scm.'
    valid_loc_types = {'HEAD': 'Head', 'TAG': 'Tag', 'BRANCH': 'Branch'}
    cvs = XML.SubElement(xml_parent, 'scm', {'class': prefix + 'CVSSCM'})
    repos = data.get('repos')
    if not repos:
        raise JenkinsJobsException("'repos' empty or missing")
    repos_tag = XML.SubElement(cvs, 'repositories')
    for repo in repos:
        repo_tag = XML.SubElement(repos_tag, prefix + 'CvsRepository')
        try:
            XML.SubElement(repo_tag, 'cvsRoot').text = repo['root']
        except KeyError:
            raise MissingAttributeError('root')
        items_tag = XML.SubElement(repo_tag, 'repositoryItems')
        locations = repo.get('locations')
        if not locations:
            raise JenkinsJobsException("'locations' empty or missing")
        for location in locations:
            item_tag = XML.SubElement(items_tag, prefix + 'CvsRepositoryItem')
            loc_type = location.get('type', 'HEAD')
            if loc_type not in valid_loc_types:
                raise InvalidAttributeError('type', loc_type, valid_loc_types)
            loc_class = ('{0}CvsRepositoryLocation${1}Repository'
                         'Location').format(prefix, valid_loc_types[loc_type])
            loc_tag = XML.SubElement(item_tag, 'location',
                                     {'class': loc_class})
            XML.SubElement(loc_tag, 'locationType').text = loc_type
            if loc_type == 'TAG' or loc_type == 'BRANCH':
                XML.SubElement(loc_tag,
                               'locationName').text = location.get('name', '')
                XML.SubElement(loc_tag, 'useHeadIfNotFound').text = str(
                    location.get('use-head', False)).lower()
            modules = location.get('modules')
            if not modules:
                raise JenkinsJobsException("'modules' empty or missing")
            modules_tag = XML.SubElement(item_tag, 'modules')
            for module in modules:
                module_tag = XML.SubElement(modules_tag, prefix + 'CvsModule')
                try:
                    XML.SubElement(module_tag,
                                   'remoteName').text = module['remote']
                except KeyError:
                    raise MissingAttributeError('remote')
                XML.SubElement(module_tag, 'localName').text = module.get(
                    'local-name', '')
        excluded = repo.get('excluded-regions', [])
        excluded_tag = XML.SubElement(repo_tag, 'excludedRegions')
        for pattern in excluded:
            pattern_tag = XML.SubElement(excluded_tag,
                                         prefix + 'ExcludedRegion')
            XML.SubElement(pattern_tag, 'pattern').text = pattern
        compression_level = repo.get('compression-level', '-1')
        if int(compression_level) not in range(-1, 10):
            raise InvalidAttributeError('compression-level', compression_level,
                                        range(-1, 10))
        XML.SubElement(repo_tag, 'compressionLevel').text = compression_level
    mapping = [('use-update', 'canUseUpdate', True),
               ('prune-empty', 'pruneEmptyDirectories', True),
               ('skip-changelog', 'skipChangeLog', False),
               ('show-all-output', 'disableCvsQuiet', False),
               ('clean-checkout', 'cleanOnFailedUpdate', False),
               ('clean-copy', 'forceCleanCopy', False)]
    for elem in mapping:
        opt, xml_tag, val = elem[:]
        XML.SubElement(cvs, xml_tag).text = str(data.get(opt, val)).lower()
示例#23
0
def main(argv=None):
    # We default argv to None and assign to sys.argv[1:] below because having
    # an argument default value be a mutable type in Python is a gotcha. See
    # http://bit.ly/1o18Vff
    if argv is None:
        argv = sys.argv[1:]

    parser = argparse.ArgumentParser()
    subparser = parser.add_subparsers(help='update, test or delete job',
                                      dest='command')
    parser_update = subparser.add_parser('update')
    parser_update.add_argument('path', help='path to YAML file or directory')
    parser_update.add_argument('names', help='name(s) of job(s)', nargs='*')
    parser_update.add_argument(
        '--delete-old',
        help='delete obsolete jobs',
        action='store_true',
        dest='delete_old',
        default=False,
    )
    parser_test = subparser.add_parser('test')
    parser_test.add_argument('path',
                             help='path to YAML file or directory',
                             nargs='?',
                             default=sys.stdin)
    parser_test.add_argument('-o',
                             dest='output_dir',
                             default=sys.stdout,
                             help='path to output XML')
    parser_test.add_argument('name', help='name(s) of job(s)', nargs='*')
    parser_delete = subparser.add_parser('delete')
    parser_delete.add_argument('name', help='name of job', nargs='+')
    parser_delete.add_argument('-p',
                               '--path',
                               default=None,
                               help='path to YAML file or directory')
    subparser.add_parser('delete-all',
                         help='delete *ALL* jobs from Jenkins server, '
                         'including those not managed by Jenkins Job '
                         'Builder.')
    parser.add_argument('--conf', dest='conf', help='configuration file')
    parser.add_argument('-l',
                        '--log_level',
                        dest='log_level',
                        default='info',
                        help="log level (default: %(default)s)")
    parser.add_argument(
        '--ignore-cache',
        action='store_true',
        dest='ignore_cache',
        default=False,
        help='ignore the cache and update the jobs anyhow (that will only '
        'flush the specified jobs cache)')
    parser.add_argument('--flush-cache',
                        action='store_true',
                        dest='flush_cache',
                        default=False,
                        help='flush all the cache entries before updating')
    options = parser.parse_args(argv)

    options.log_level = getattr(logging, options.log_level.upper(),
                                logging.INFO)
    logging.basicConfig(level=options.log_level)
    logger = logging.getLogger()

    conf = '/etc/jenkins_jobs/jenkins_jobs.ini'
    if options.conf:
        conf = options.conf
    else:
        # Fallback to script directory
        localconf = os.path.join(os.path.dirname(__file__), 'jenkins_jobs.ini')
        if os.path.isfile(localconf):
            conf = localconf
    config = ConfigParser.ConfigParser()
    ## Load default config always
    config.readfp(cStringIO.StringIO(DEFAULT_CONF))
    if options.command == 'test':
        logger.debug("Not reading config for test output generation")
    elif os.path.isfile(conf):
        logger.debug("Reading config from {0}".format(conf))
        conffp = open(conf, 'r')
        config.readfp(conffp)
    else:
        raise JenkinsJobsException(
            "A valid configuration file is required when not run as a test"
            "\n{0} is not a valid .ini file".format(conf))

    execute(options, config, logger)
示例#24
0
def git(parser, xml_parent, data):
    """yaml: git
    Specifies the git SCM repository for this job.
    Requires the Jenkins :jenkins-wiki:`Git Plugin <Git+Plugin>`.

    :arg str url: URL of the git repository
    :arg str credentials-id: ID of credential to use to connect, which is the
      last field(a 32-digit hexadecimal code) of the path of URL visible after
      you clicked the credential under Jenkins Global credentials. (optional)
    :arg str refspec: refspec to fetch (default '+refs/heads/\*:refs/remotes/\
remoteName/\*')
    :arg str name: name to fetch (default 'origin')
    :arg list(str) remotes: list of remotes to set up (optional, only needed if
      multiple remotes need to be set up)

        :Remote: * **url** (`string`) - url of remote repo
                 * **refspec** (`string`) - refspec to fetch (optional)
                 * **credentials-id** - ID of credential to use to connect,
                   which is the last field of the path of URL
                   (a 32-digit hexadecimal code) visible after you clicked
                   credential under Jenkins Global credentials. (optional)
    :arg list(str) branches: list of branch specifiers to build (default '**')
    :arg list(str) excluded-users: list of users to ignore revisions from
      when polling for changes. (if polling is enabled, optional)
    :arg list(str) included-regions: list of file/folders to include (optional)
    :arg list(str) excluded-regions: list of file/folders to exclude (optional)
    :arg str local-branch: Checkout/merge to local branch (optional)
    :arg dict merge:
        :merge:
            * **remote** (`string`) - name of repo that contains branch to
              merge to (default 'origin')
            * **branch** (`string`) - name of the branch to merge to
            * **strategy** (`string`) - merge strategy. Can be one of
              'default', 'resolve', 'recursive', 'octopus', 'ours',
              'subtree'. (default 'default')
            * **fast-forward-mode** (`string`) - merge fast-forward mode.
              Can be one of 'FF', 'FF_ONLY' or 'NO_FF'. (default 'FF')
    :arg str basedir: location relative to the workspace root to clone to
             (default: workspace)
    :arg bool skip-tag: Skip tagging (default false)
    :arg bool shallow-clone: Perform shallow clone (default false)
    :arg bool prune: Prune remote branches (default false)
    :arg bool clean: Clean after checkout (default false)

        .. deprecated:: 1.1.1. Please use clean extension format.

    :arg bool fastpoll: Use fast remote polling (default false)
    :arg bool disable-submodules: Disable submodules (default false)

        .. deprecated:: 1.1.1. Please use submodule extension.

    :arg bool recursive-submodules: Recursively update submodules (default
      false)

        .. deprecated:: 1.1.1. Please use submodule extension.

    :arg bool use-author: Use author rather than committer in Jenkin's build
      changeset (default false)
    :arg str git-tool: The name of the Git installation to use (default
      'Default')
    :arg str reference-repo: Path of the reference repo to use during clone
      (optional)
    :arg str scm-name: The unique scm name for this Git SCM (optional)
    :arg bool ignore-notify: Ignore notifyCommit URL accesses (default false)
    :arg str browser: what repository browser to use (default '(Auto)')
    :arg str browser-url: url for the repository browser (required if browser
      is not '(Auto)', no default)
    :arg str browser-version: version of the repository browser (GitLab only,
      default '0.0')
    :arg str project-name: project name in Gitblit and ViewGit repobrowser
      (optional)
    :arg str repo-name: repository name in phabricator repobrowser (optional)
    :arg str choosing-strategy: Jenkins class for selecting what to build
      (default 'default')
    :arg str git-config-name: Configure name for Git clone (optional)
    :arg str git-config-email: Configure email for Git clone (optional)


    :extensions:
        :arg dict changelog-against:
            :changelog-against:
                * **remote** (`string`) - name of repo that contains branch to
                  create changelog against (default 'origin')
                * **branch** (`string`) - name of the branch to create
                  changelog against (default 'master')

        :arg dict clean:
            :clean:
                * **after** (`bool`) - Clean the workspace after checkout
                * **before** (`bool`) - Clean the workspace before checkout

        :arg list(str) ignore-commits-with-messages: Revisions committed with
            messages matching these patterns will be ignored. (optional)

        :arg bool force-polling-using-workspace: Force polling using workspace
            (default false)

        :arg dict sparse-checkout:
            :sparse-checkout:
                * **paths** (`list`) - List of paths to sparse checkout.
                  (optional)

        :arg dict submodule:
            :submodule:
                * **disable** (`bool`) - By disabling support for submodules
                  you can still keep using basic git plugin functionality
                  and just have Jenkins to ignore submodules completely as
                  if they didn't exist.
                * **recursive** (`bool`) - Retrieve all submodules recursively
                  (uses '--recursive' option which requires git>=1.6.5)
                * **tracking** (`bool`) - Retrieve the tip of the configured
                  branch in .gitmodules (Uses '--remote' option which
                  requires git>=1.8.2)
                * **timeout** (`int`) - Specify a timeout (in minutes) for
                  submodules operations (default: 10).

        :arg str timeout: Timeout for git commands in minutes (optional)
        :arg bool wipe-workspace: Wipe out workspace before build
          (default true)

    :browser values:
        :auto:
        :assemblaweb:
        :bitbucketweb:
        :cgit:
        :fisheye:
        :gitblit:
        :githubweb:
        :gitiles:
        :gitlab:
        :gitlist:
        :gitoriousweb:
        :gitweb:
        :kiln:
        :microsoft-tfs-2013:
        :phabricator:
        :redmineweb:
        :rhodecode:
        :stash:
        :viewgit:

    :choosing-strategy values:
        :default:
        :inverse:
        :gerrit:

    Example:

    .. literalinclude:: /../../tests/scm/fixtures/git001.yaml
    """
    logger = logging.getLogger("%s:git" % __name__)

    # XXX somebody should write the docs for those with option name =
    # None so we have a sensible name/key for it.
    mapping = [
        # option, xml name, default value (text), attributes (hard coded)
        ("disable-submodules", 'disableSubmodules', False),
        ("recursive-submodules", 'recursiveSubmodules', False),
        (None, 'doGenerateSubmoduleConfigurations', False),
        ("use-author", 'authorOrCommitter', False),
        ("wipe-workspace", 'wipeOutWorkspace', True),
        ("prune", 'pruneBranches', False),
        ("fastpoll", 'remotePoll', False),
        ("git-tool", 'gitTool', "Default"),
        (None, 'submoduleCfg', '', {
            'class': 'list'
        }),
        ('basedir', 'relativeTargetDir', ''),
        ('reference-repo', 'reference', ''),
        ("git-config-name", 'gitConfigName', ''),
        ("git-config-email", 'gitConfigEmail', ''),
        ('skip-tag', 'skipTag', False),
        ('scm-name', 'scmName', ''),
        ("shallow-clone", "useShallowClone", False),
        ("ignore-notify", "ignoreNotifyCommit", False),
    ]

    choosing_strategies = {
        'default':
        'hudson.plugins.git.util.DefaultBuildChooser',
        'gerrit': ('com.sonyericsson.hudson.plugins.'
                   'gerrit.trigger.hudsontrigger.GerritTriggerBuildChooser'),
        'inverse':
        'hudson.plugins.git.util.InverseBuildChooser',
    }

    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.plugins.git.GitSCM'})
    XML.SubElement(scm, 'configVersion').text = '2'
    user = XML.SubElement(scm, 'userRemoteConfigs')
    if 'remotes' not in data:
        data['remotes'] = [{data.get('name', 'origin'): data.copy()}]
    for remoteData in data['remotes']:
        huser = XML.SubElement(user, 'hudson.plugins.git.UserRemoteConfig')
        remoteName = next(iter(remoteData.keys()))
        XML.SubElement(huser, 'name').text = remoteName
        remoteParams = next(iter(remoteData.values()))
        if 'refspec' in remoteParams:
            refspec = remoteParams['refspec']
        else:
            refspec = '+refs/heads/*:refs/remotes/' + remoteName + '/*'
        XML.SubElement(huser, 'refspec').text = refspec
        if 'url' in remoteParams:
            remoteURL = remoteParams['url']
        else:
            raise JenkinsJobsException('Must specify a url for git remote \"' +
                                       remoteName + '"')
        XML.SubElement(huser, 'url').text = remoteURL
        if 'credentials-id' in remoteParams:
            credentialsId = remoteParams['credentials-id']
            XML.SubElement(huser, 'credentialsId').text = credentialsId
    xml_branches = XML.SubElement(scm, 'branches')
    branches = data.get('branches', ['**'])
    for branch in branches:
        bspec = XML.SubElement(xml_branches, 'hudson.plugins.git.BranchSpec')
        XML.SubElement(bspec, 'name').text = branch
    excluded_users = '\n'.join(data.get('excluded-users', []))
    XML.SubElement(scm, 'excludedUsers').text = excluded_users
    if 'included-regions' in data:
        include_string = '\n'.join(data['included-regions'])
        XML.SubElement(scm, 'includedRegions').text = include_string
    if 'excluded-regions' in data:
        exclude_string = '\n'.join(data['excluded-regions'])
        XML.SubElement(scm, 'excludedRegions').text = exclude_string
    if 'merge' in data:
        merge = data['merge']
        merge_strategies = [
            'default', 'resolve', 'recursive', 'octopus', 'ours', 'subtree'
        ]
        fast_forward_modes = ['FF', 'FF_ONLY', 'NO_FF']
        name = merge.get('remote', 'origin')
        branch = merge['branch']
        urc = XML.SubElement(scm, 'userMergeOptions')
        XML.SubElement(urc, 'mergeRemote').text = name
        XML.SubElement(urc, 'mergeTarget').text = branch
        strategy = merge.get('strategy', 'default')
        if strategy not in merge_strategies:
            raise InvalidAttributeError('strategy', strategy, merge_strategies)
        XML.SubElement(urc, 'mergeStrategy').text = strategy
        fast_forward_mode = merge.get('fast-forward-mode', 'FF')
        if fast_forward_mode not in fast_forward_modes:
            raise InvalidAttributeError('fast-forward-mode', fast_forward_mode,
                                        fast_forward_modes)
        XML.SubElement(urc, 'fastForwardMode').text = fast_forward_mode

    try:
        choosing_strategy = choosing_strategies[data.get(
            'choosing-strategy', 'default')]
    except KeyError:
        raise ValueError('Invalid choosing-strategy %r' %
                         data.get('choosing-strategy'))
    XML.SubElement(scm, 'buildChooser', {'class': choosing_strategy})

    for elem in mapping:
        (optname, xmlname, val) = elem[:3]

        # Throw warning for deprecated settings and skip if the 'submodule' key
        # is available.
        submodule_cfgs = ['disable-submodules', 'recursive-submodules']
        if optname in submodule_cfgs:
            if optname in data:
                logger.warn("'{0}' is deprecated, please convert to use the "
                            "'submodule' section instead as support for this "
                            "top level option will be removed in a future "
                            "release.".format(optname))
            if 'submodule' in data:
                continue

        attrs = {}
        if len(elem) >= 4:
            attrs = elem[3]
        xe = XML.SubElement(scm, xmlname, attrs)
        if optname and optname in data:
            val = data[optname]
        if type(val) == bool:
            xe.text = str(val).lower()
        else:
            xe.text = val

    if 'local-branch' in data:
        XML.SubElement(scm, 'localBranch').text = data['local-branch']

    exts_node = XML.SubElement(scm, 'extensions')
    impl_prefix = 'hudson.plugins.git.extensions.impl.'
    if 'changelog-against' in data:
        ext_name = impl_prefix + 'ChangelogToBranch'
        ext = XML.SubElement(exts_node, ext_name)
        opts = XML.SubElement(ext, 'options')
        change_remote = data['changelog-against'].get('remote', 'origin')
        change_branch = data['changelog-against'].get('branch', 'master')
        XML.SubElement(opts, 'compareRemote').text = change_remote
        XML.SubElement(opts, 'compareTarget').text = change_branch
    if 'clean' in data:
        # Keep support for old format 'clean' configuration by checking
        # if 'clean' is boolean. Else we're using the new extensions style.
        if isinstance(data['clean'], bool):
            clean_after = data['clean']
            clean_before = False
            logger.warn("'clean: bool' configuration format is deprecated, "
                        "please use the extension style format to configure "
                        "this option.")
        else:
            clean_after = data['clean'].get('after', False)
            clean_before = data['clean'].get('before', False)
        if clean_after:
            ext_name = impl_prefix + 'CleanCheckout'
            ext = XML.SubElement(exts_node, ext_name)
        if clean_before:
            ext_name = impl_prefix + 'CleanBeforeCheckout'
            ext = XML.SubElement(exts_node, ext_name)
    if 'ignore-commits-with-messages' in data:
        for msg in data['ignore-commits-with-messages']:
            ext_name = impl_prefix + 'MessageExclusion'
            ext = XML.SubElement(exts_node, ext_name)
            XML.SubElement(ext, 'excludedMessage').text = msg
    if 'sparse-checkout' in data:
        ext_name = impl_prefix + 'SparseCheckoutPaths'
        ext = XML.SubElement(exts_node, ext_name)
        sparse_co = XML.SubElement(ext, 'sparseCheckoutPaths')
        sparse_paths = data['sparse-checkout'].get('paths')
        if sparse_paths is not None:
            path_tagname = impl_prefix + 'SparseCheckoutPath'
            for path in sparse_paths:
                path_tag = XML.SubElement(sparse_co, path_tagname)
                XML.SubElement(path_tag, 'path').text = path
    if 'submodule' in data:
        ext_name = impl_prefix + 'SubmoduleOption'
        ext = XML.SubElement(exts_node, ext_name)
        XML.SubElement(ext,
                       'disableSubmodules').text = str(data['submodule'].get(
                           'disable', False)).lower()
        XML.SubElement(ext,
                       'recursiveSubmodules').text = str(data['submodule'].get(
                           'recursive', False)).lower()
        XML.SubElement(ext,
                       'trackingSubmodules').text = str(data['submodule'].get(
                           'tracking', False)).lower()
        XML.SubElement(ext, 'timeout').text = str(data['submodule'].get(
            'timeout', 10))
    if 'timeout' in data:
        co = XML.SubElement(exts_node, impl_prefix + 'CheckoutOption')
        XML.SubElement(co, 'timeout').text = str(data['timeout'])
    polling_using_workspace = str(
        data.get('force-polling-using-workspace', False)).lower()
    if polling_using_workspace == 'true':
        ext_name = impl_prefix + 'DisableRemotePoll'
        ext = XML.SubElement(exts_node, ext_name)
    # By default we wipe the workspace
    wipe_workspace = str(data.get('wipe-workspace', True)).lower()
    if wipe_workspace == 'true':
        ext_name = impl_prefix + 'WipeWorkspace'
        ext = XML.SubElement(exts_node, ext_name)

    browser = data.get('browser', 'auto')
    browserdict = {
        'auto': 'auto',
        'assemblaweb': 'AssemblaWeb',
        'bitbucketweb': 'BitbucketWeb',
        'cgit': 'CGit',
        'fisheye': 'FisheyeGitRepositoryBrowser',
        'gitblit': 'GitBlitRepositoryBrowser',
        'githubweb': 'GithubWeb',
        'gitiles': 'Gitiles',
        'gitlab': 'GitLab',
        'gitlist': 'GitList',
        'gitoriousweb': 'GitoriousWeb',
        'gitweb': 'GitWeb',
        'kiln': 'KilnGit',
        'microsoft-tfs-2013': 'TFS2013GitRepositoryBrowser',
        'phabricator': 'Phabricator',
        'redmineweb': 'RedmineWeb',
        'rhodecode': 'RhodeCode',
        'stash': 'Stash',
        'viewgit': 'ViewGitWeb'
    }
    if browser not in browserdict:
        valid = sorted(browserdict.keys())
        raise JenkinsJobsException("Browser entered is not valid must be one "
                                   "of: %s or %s." %
                                   (", ".join(valid[:-1]), valid[-1]))
    if browser != 'auto':
        bc = XML.SubElement(
            scm, 'browser',
            {'class': 'hudson.plugins.git.browser.' + browserdict[browser]})
        XML.SubElement(bc, 'url').text = data['browser-url']
        if browser in ['gitblit', 'viewgit']:
            XML.SubElement(bc, 'projectName').text = str(
                data.get('project-name', ''))
        if browser == 'gitlab':
            XML.SubElement(bc, 'version').text = str(
                data.get('browser-version', '0.0'))
        if browser == 'phabricator':
            XML.SubElement(bc, 'repo').text = str(data.get('repo-name', ''))
示例#25
0
    def expandYaml(self, registry, jobs_glob=None):
        changed = True
        while changed:
            changed = False
            for module in registry.modules:
                if hasattr(module, 'handle_data'):
                    if module.handle_data(self.data):
                        changed = True

        self._register_macros()
        for default in self.data.get('defaults', {}).values():
            self._macro_registry.expand_macros(default)
        for job in self.data.get('job', {}).values():
            self._macro_registry.expand_macros(job)
            if jobs_glob and not matches(job['name'], jobs_glob):
                logger.debug("Ignoring job {0}".format(job['name']))
                continue
            logger.debug("Expanding job '{0}'".format(job['name']))
            job = self._applyDefaults(job)
            self._formatDescription(job)
            self.jobs.append(job)

        for view in self.data.get('view', {}).values():
            logger.debug("Expanding view '{0}'".format(view['name']))
            self._formatDescription(view)
            self.views.append(view)

        for project in self.data.get('project', {}).values():
            logger.debug("Expanding project '{0}'".format(project['name']))
            # use a set to check for duplicate job references in projects
            seen = set()
            for jobspec in project.get('jobs', []):
                if isinstance(jobspec, dict):
                    # Singleton dict containing dict of job-specific params
                    jobname, jobparams = next(iter(jobspec.items()))
                    if not isinstance(jobparams, dict):
                        jobparams = {}
                else:
                    jobname = jobspec
                    jobparams = {}
                job = self._getJob(jobname)
                if job:
                    # Just naming an existing defined job
                    if jobname in seen:
                        self._handle_dups("Duplicate job '{0}' specified "
                                          "for project '{1}'"
                                          .format(jobname, project['name']))
                    seen.add(jobname)
                    continue
                # see if it's a job group
                group = self._getJobGroup(jobname)
                if group:
                    for group_jobspec in group['jobs']:
                        if isinstance(group_jobspec, dict):
                            group_jobname, group_jobparams = \
                                next(iter(group_jobspec.items()))
                            if not isinstance(group_jobparams, dict):
                                group_jobparams = {}
                        else:
                            group_jobname = group_jobspec
                            group_jobparams = {}
                        job = self._getJob(group_jobname)
                        if job:
                            if group_jobname in seen:
                                self._handle_dups(
                                    "Duplicate job '{0}' specified for "
                                    "project '{1}'".format(group_jobname,
                                                           project['name']))
                            seen.add(group_jobname)
                            continue
                        template = self._getJobTemplate(group_jobname)
                        # Allow a group to override parameters set by a project
                        d = type(project)(project)
                        d.update(jobparams)
                        d.update(group)
                        d.update(group_jobparams)
                        # Except name, since the group's name is not useful
                        d['name'] = project['name']
                        if template:
                            self._expandYamlForTemplateJob(d, template,
                                                           jobs_glob)
                    continue
                # see if it's a template
                template = self._getJobTemplate(jobname)
                if template:
                    d = type(project)(project)
                    d.update(jobparams)
                    self._expandYamlForTemplateJob(d, template, jobs_glob)
                else:
                    raise JenkinsJobsException("Failed to find suitable "
                                               "template named '{0}'"
                                               .format(jobname))
        # check for duplicate generated jobs
        seen = set()
        # walk the list in reverse so that last definition wins
        for job in self.jobs[::-1]:
            if job['name'] in seen:
                self._handle_dups("Duplicate definitions for job '{0}' "
                                  "specified".format(job['name']))
                self.jobs.remove(job)
            seen.add(job['name'])
        return self.jobs, self.views
示例#26
0
def hg(self, xml_parent, data):
    """yaml: hg
    Specifies the mercurial SCM repository for this job.
    Requires the Jenkins :jenkins-wiki:`Mercurial Plugin <Mercurial+Plugin>`.

    :arg str url: URL of the hg repository
    :arg str credentials-id: ID of credentials to use to connect (optional)
    :arg str revision-type: revision type to use (default 'branch')
    :arg str revision: the branch or tag name you would like to track
      (default 'default')
    :arg list(str) modules: reduce unnecessary builds by specifying a list of
      "modules" within the repository. A module is a directory name within the
      repository that this project lives in. (default '')
    :arg bool clean: wipe any local modifications or untracked files in the
      repository checkout (default false)
    :arg str subdir: check out the Mercurial repository into this
      subdirectory of the job's workspace (optional)
    :arg bool disable-changelog: do not calculate the Mercurial changelog
      for each build (default false)
    :arg str browser: what repository browser to use (default 'auto')
    :arg str browser-url: url for the repository browser
      (required if browser is set)

    :browser values:
        :fisheye:
        :bitbucketweb:
        :googlecode:
        :hgweb:
        :kilnhg:
        :rhodecode:
        :rhodecodelegacy:

    Example:

    .. literalinclude:: ../../tests/scm/fixtures/hg02.yaml
    """
    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.plugins.mercurial.MercurialSCM'})
    if 'url' in data:
        XML.SubElement(scm, 'source').text = data['url']
    else:
        raise JenkinsJobsException("A top level url must exist")

    if 'credentials-id' in data:
        XML.SubElement(scm, 'credentialsId').text = data['credentials-id']

    revision_type_dict = {
        'branch': 'BRANCH',
        'tag': 'TAG',
    }
    try:
        revision_type = revision_type_dict[data.get('revision-type', 'branch')]
    except KeyError:
        raise JenkinsJobsException('Invalid revision-type %r' %
                                   data.get('revision-type'))
    XML.SubElement(scm, 'revisionType').text = revision_type

    XML.SubElement(scm, 'revision').text = data.get('revision', 'default')

    if 'subdir' in data:
        XML.SubElement(scm, 'subdir').text = data['subdir']

    xc = XML.SubElement(scm, 'clean')
    xc.text = str(data.get('clean', False)).lower()

    modules = data.get('modules', '')
    if isinstance(modules, list):
        modules = " ".join(modules)
    XML.SubElement(scm, 'modules').text = modules

    xd = XML.SubElement(scm, 'disableChangeLog')
    xd.text = str(data.get('disable-changelog', False)).lower()

    browser = data.get('browser', 'auto')
    browserdict = {
        'auto': '',
        'bitbucket': 'BitBucket',
        'fisheye': 'FishEye',
        'googlecode': 'GoogleCode',
        'hgweb': 'HgWeb',
        'kilnhg': 'KilnHG',
        'rhodecode': 'RhodeCode',
        'rhodecode-pre-1.2.0': 'RhodeCodeLegacy'
    }

    if browser not in browserdict:
        raise JenkinsJobsException("Browser entered is not valid must be one "
                                   "of: %s" % ", ".join(browserdict.keys()))
    if browser != 'auto':
        bc = XML.SubElement(scm, 'browser', {
            'class':
            'hudson.plugins.mercurial.browser.' + browserdict[browser]
        })
        if 'browser-url' in data:
            XML.SubElement(bc, 'url').text = data['browser-url']
        else:
            raise JenkinsJobsException("A browser-url must be specified along "
                                       "with browser.")
示例#27
0
def extended_choice_param(parser, xml_parent, data):
    """yaml: extended-choice
    Creates an extended choice parameter where values can be read from a file
    Requires the Jenkins :jenkins-wiki:`Extended Choice Parameter Plugin
    <Extended+Choice+Parameter+plugin>`.

    :arg str name: name of the parameter
    :arg str description: description of the parameter
        (optional, default '')
    :arg str property-file: location of property file to read from
        (optional, default '')
    :arg str property-key: key for the property-file (optional, default '')
    :arg bool quote-value: whether to put quotes around the property
        when passing to Jenkins (optional, default false)
    :arg str visible-items: number of items to show in the list
        (optional, default 5)
    :arg str type: type of select, can be single-select, multi-select,
        radio, checkbox or textbox (optional, default single-select)
    :arg str value: comma separated list of values for the single select
        or multi-select box (optional, default '')
    :arg str default-value: used to set the initial selection of the
        single-select or multi-select box (optional, default '')
    :arg str default-property-file: location of property file when default
        value needs to come from a property file (optional, default '')
    :arg str default-property-key: key for the default property file
        (optional, default '')
    :arg str multi-select-delimiter: value between selections when the
        parameter is a multi-select (optiona, default ',')

    Example:

    .. literalinclude:: \
    /../../tests/parameters/fixtures/extended-choice-param001.yaml
       :language: yaml

    """
    pdef = base_param(parser, xml_parent, data, False,
                      'com.cwctravel.hudson.plugins.'
                      'extended__choice__parameter.'
                      'ExtendedChoiceParameterDefinition')
    XML.SubElement(pdef, 'value').text = data.get('value', '')
    XML.SubElement(pdef, 'visibleItemCount').text = str(data.get(
        'visible-items', data.get('visible-item-count', 5)))
    XML.SubElement(pdef, 'multiSelectDelimiter').text = data.get(
        'multi-select-delimiter', ',')
    XML.SubElement(pdef, 'quoteValue').text = str(data.get('quote-value',
                                                  False)).lower()
    XML.SubElement(pdef, 'defaultValue').text = data.get(
        'default-value', '')

    choice = data.get('type', 'single-select')
    choicedict = {'single-select': 'PT_SINGLE_SELECT',
                  'multi-select': 'PT_MULTI_SELECT',
                  'radio': 'PT_RADIO',
                  'checkbox': 'PT_CHECKBOX',
                  'textbox': 'PT_TEXTBOX',
                  'PT_SINGLE_SELECT': 'PT_SINGLE_SELECT',
                  'PT_MULTI_SELECT': 'PT_MULTI_SELECT',
                  'PT_RADIO': 'PT_RADIO',
                  'PT_CHECKBOX': 'PT_CHECKBOX',
                  'PT_TEXTBOX': 'PT_TEXTBOX'}

    if choice in choicedict:
        XML.SubElement(pdef, 'type').text = choicedict[choice]
    else:
        raise JenkinsJobsException("Type entered is not valid, must be one "
                                   "of: single-select, multi-select, radio, "
                                   "textbox or checkbox")
    XML.SubElement(pdef, 'propertyFile').text = data.get('property-file', '')
    XML.SubElement(pdef, 'propertyKey').text = data.get('property-key', '')
    XML.SubElement(pdef, 'defaultPropertyFile').text = data.get(
        'default-property-file', '')
    XML.SubElement(pdef, 'defaultPropertyKey').text = data.get(
        'default-property-key', '')
示例#28
0
def repo(parser, xml_parent, data):
    """yaml: repo
    Specifies the repo SCM repository for this job.
    Requires the Jenkins `Repo Plugin.
    <https://wiki.jenkins-ci.org/display/JENKINS/Repo+Plugin>`_

    :arg str manifest-url: URL of the repo manifest
    :arg str manifest-branch: The branch of the manifest to use (optional)
    :arg str manifest-file: Initial manifest file to use when initialising
             (optional)
    :arg str manifest-group: Only retrieve those projects in the manifest
             tagged with the provided group name (optional)
    :arg str destination-dir: Location relative to the workspace root to clone
             under (optional)
    :arg str repo-url: custom url to retrieve the repo application (optional)
    :arg str mirror-dir: Path to mirror directory to reference when
             initialising (optional)
    :arg int jobs: Number of projects to fetch simultaneously (default 0)
    :arg bool current-branch: Fetch only the current branch from the server
              (default true)
    :arg bool quiet: Make repo more quiet
              (default true)
    :arg str local-manifest: Contents of .repo/local_manifest.xml, written
             prior to calling sync (optional)

    Example:

    .. literalinclude:: /../../tests/scm/fixtures/repo001.yaml
    """

    scm = XML.SubElement(xml_parent, 'scm',
                         {'class': 'hudson.plugins.repo.RepoScm'})

    if 'manifest-url' in data:
        XML.SubElement(scm, 'manifestRepositoryUrl').text = \
            data['manifest-url']
    else:
        raise JenkinsJobsException("Must specify a manifest url")

    mapping = [
        # option, xml name, default value
        ("manifest-branch", 'manifestBranch', ''),
        ("manifest-file", 'manifestFile', ''),
        ("manifest-group", 'manifestGroup', ''),
        ("destination-dir", 'destinationDir', ''),
        ("repo-url", 'repoUrl', ''),
        ("mirror-dir", 'mirrorDir', ''),
        ("jobs", 'jobs', 0),
        ("current-branch", 'currentBranch', True),
        ("quiet", 'quiet', True),
        ("local-manifest", 'localManifest', ''),
    ]

    for elem in mapping:
        (optname, xmlname, val) = elem
        val = data.get(optname, val)
        # Skip adding xml entry if default is empty string and no value given
        if not val and elem[2] is '':
            continue
        xe = XML.SubElement(scm, xmlname)
        if type(elem[2]) == bool:
            xe.text = str(val).lower()
        else:
            xe.text = str(val)
示例#29
0
    def dispatch(self,
                 component_type,
                 parser,
                 xml_parent,
                 component,
                 template_data={}):
        """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
        entry points and Jenkins Job Builder :ref:`Macros <macro>`.

        :arg string component_type: the name of the component
          (e.g., `builder`)
        :arg YAMLParser parser: the global YMAL Parser
        :arg Element xml_parent: the parent XML element
        :arg dict template_data: values that should be interpolated into
          the component definition

        See :py:class:`jenkins_jobs.modules.base.Base` for how to register
        components of a module.

        See the Publishers module for a simple example of how to use
        this method.
        """

        if component_type not in self.modules_by_component_type:
            raise JenkinsJobsException("Unknown component type: "
                                       "'{0}'.".format(component_type))

        component_list_type = self.modules_by_component_type[component_type] \
            .component_list_type

        if isinstance(component, dict):
            # The component is a sigleton dictionary of name: dict(args)
            name, component_data = component.items()[0]
            if template_data:
                # Template data contains values that should be interpolated
                # into the component definition
                s = yaml.dump(component_data, default_flow_style=False)
                s = s.format(**template_data)
                component_data = yaml.load(s)
        else:
            # The component is a simple string name, eg "run-tests"
            name = component
            component_data = {}

        # Look for a component function defined in an entry point
        cache_key = '%s:%s' % (component_list_type, name)
        eps = ModuleRegistry.entry_points_cache.get(cache_key)
        if eps is None:
            eps = list(
                pkg_resources.iter_entry_points(
                    group='jenkins_jobs.{0}'.format(component_list_type),
                    name=name))
            if len(eps) > 1:
                raise JenkinsJobsException(
                    "Duplicate entry point found for component type: '{0}',"
                    "name: '{1}'".format(component_type, name))
            elif len(eps) == 1:
                ModuleRegistry.entry_points_cache[cache_key] = eps
                logger.debug("Cached entry point %s = %s", cache_key,
                             ModuleRegistry.entry_points_cache[cache_key])

        if len(eps) == 1:
            func = eps[0].load()
            func(parser, xml_parent, component_data)
        else:
            # Otherwise, see if it's defined as a macro
            component = parser.data.get(component_type, {}).get(name)
            if component:
                for b in component[component_list_type]:
                    # 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, parser, xml_parent, b,
                                  component_data)
            else:
                raise JenkinsJobsException(
                    "Unknown entry point or macro '{0}'"
                    " for component type: '{1}'.".format(name, component_type))
示例#30
0
def shining_panda(parser, xml_parent, data):
    """yaml: shining-panda
    Execute a command inside various python environments. Requires the Jenkins
    `ShiningPanda plugin
    <https://wiki.jenkins-ci.org/display/JENKINS/ShiningPanda+Plugin>`_.

    :arg str build-environment: Building environment to set up (Required).

        :build-environment values:
            * **python**: Use a python installation configured in Jenkins.
            * **custom**: Use a manually installed python.
            * **virtualenv**: Create a virtualenv

    For the **python** environment

    :arg str python-version: Name of the python installation to use.
        Must match one of the configured installations on server \
        configuration
        (default: System-CPython-2.7)

    For the **custom** environment:

    :arg str home: path to the home folder of the custom installation \
        (Required)

    For the **virtualenv** environment:

    :arg str python-version: Name of the python installation to use.
        Must match one of the configured installations on server \
        configuration
        (default: System-CPython-2.7)
    :arg str name: Name of this virtualenv. Two virtualenv builders with \
        the same name will use the same virtualenv installation (optional)
    :arg bool clear: If true, delete and recreate virtualenv on each build.
        (default: false)
    :arg bool use-distribute: if true use distribute, if false use \
        setuptools. (default: true)
    :arg bool system-site-packages: if true, give access to the global
        site-packages directory to the virtualenv. (default: false)

    Common to all environments:

    :arg str nature: Nature of the command field. (default: shell)

        :nature values:
            * **shell**: execute the Command contents with default shell
            * **xshell**: like **shell** but performs platform conversion \
                first
            * **python**: execute the Command contents with the Python \
                executable

    :arg str command: The command to execute
    :arg bool ignore-exit-code: mark the build as failure if any of the
        commands exits with a non-zero exit code. (default: false)

    Examples:

    .. literalinclude:: \
        /../../tests/builders/fixtures/shining-panda-pythonenv.yaml
       :language: yaml

    .. literalinclude:: \
        /../../tests/builders/fixtures/shining-panda-customenv.yaml
       :language: yaml

    .. literalinclude:: \
        /../../tests/builders/fixtures/shining-panda-virtualenv.yaml
       :language: yaml
    """

    pluginelementpart = 'jenkins.plugins.shiningpanda.builders.'
    buildenvdict = {
        'custom': 'CustomPythonBuilder',
        'virtualenv': 'VirtualenvBuilder',
        'python': 'PythonBuilder'
    }
    envs = (buildenvdict.keys())

    try:
        buildenv = data['build-environment']
    except KeyError:
        raise JenkinsJobsException("A build-environment is required")

    if buildenv not in envs:
        errorstring = (
            "build-environment '%s' is invalid. Must be one of %s." %
            (buildenv, ', '.join("'{0}'".format(env) for env in envs)))
        raise JenkinsJobsException(errorstring)

    t = XML.SubElement(xml_parent,
                       '%s%s' % (pluginelementpart, buildenvdict[buildenv]))

    if buildenv in ('python', 'virtualenv'):
        XML.SubElement(t,
                       'pythonName').text = data.get("python-version",
                                                     "System-CPython-2.7")

    if buildenv in ('custom'):
        try:
            homevalue = data["home"]
        except KeyError:
            raise JenkinsJobsException("'home' argument is required for the"
                                       " 'custom' environment")
        XML.SubElement(t, 'home').text = homevalue

    if buildenv in ('virtualenv'):
        XML.SubElement(t, 'home').text = data.get("name", "")
        clear = data.get("clear", False)
        XML.SubElement(t, 'clear').text = str(clear).lower()
        use_distribute = data.get('use-distribute', False)
        XML.SubElement(t, 'useDistribute').text = str(use_distribute).lower()
        system_site_packages = data.get('system-site-packages', False)
        XML.SubElement(
            t, 'systemSitePackages').text = str(system_site_packages).lower()

    # Common arguments
    nature = data.get('nature', 'shell')
    naturetuple = ('shell', 'xshell', 'python')
    if nature not in naturetuple:
        errorstring = ("nature '%s' is not valid: must be one of %s." %
                       (nature, ', '.join("'{0}'".format(naturevalue)
                                          for naturevalue in naturetuple)))
        raise JenkinsJobsException(errorstring)
    XML.SubElement(t, 'nature').text = nature
    XML.SubElement(t, 'command').text = data.get("command", "")
    ignore_exit_code = data.get('ignore-exit-code', False)
    XML.SubElement(t, 'ignoreExitCode').text = str(ignore_exit_code).lower()