From 5465ab6d4fe36ec6aa853f8252c06df37bdf9ed8 Mon Sep 17 00:00:00 2001 From: Manuel Desbonnet Date: Wed, 19 Sep 2012 08:52:35 +0100 Subject: [PATCH] Adding hipchat notification capability. Tweaks the Builder and YamlParser classes to accept a config object which is passed to the parser's ModuleRegistry object. This makes it available to the HipChat object. Change-Id: I3017658336b949c0fda7c82945e7014dbcf6e152 Reviewed-on: https://review.openstack.org/12794 Reviewed-by: James E. Blair Reviewed-by: Clark Boylan Approved: James E. Blair Tested-by: Jenkins --- doc/source/configuration.rst | 1 + doc/source/hipchat.rst | 7 ++ jenkins-jobs | 3 +- jenkins_jobs/builder.py | 13 ++-- jenkins_jobs/modules/hipchat_notif.py | 106 ++++++++++++++++++++++++++ setup.py | 1 + 6 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 doc/source/hipchat.rst create mode 100644 jenkins_jobs/modules/hipchat_notif.py diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 8ccee96ce..c835c8bf9 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -184,6 +184,7 @@ The bulk of the job definitions come from the following modules. project_maven general builders + hipchat notifications parameters properties diff --git a/doc/source/hipchat.rst b/doc/source/hipchat.rst new file mode 100644 index 000000000..94e841b15 --- /dev/null +++ b/doc/source/hipchat.rst @@ -0,0 +1,7 @@ +.. _hipchat: + +Hipchat +========== + +.. automodule:: hipchat_notif + :members: diff --git a/jenkins-jobs b/jenkins-jobs index 5e1bf2706..a57ee12d2 100755 --- a/jenkins-jobs +++ b/jenkins-jobs @@ -45,7 +45,8 @@ def main(): logger.debug("Config: {0}".format(config)) builder = jenkins_jobs.builder.Builder(config.get('jenkins', 'url'), config.get('jenkins', 'user'), - config.get('jenkins', 'password')) + config.get('jenkins', 'password'), + config) if options.command == 'delete': for job in options.name: diff --git a/jenkins_jobs/builder.py b/jenkins_jobs/builder.py index 77b0bab93..d25853fb7 100644 --- a/jenkins_jobs/builder.py +++ b/jenkins_jobs/builder.py @@ -29,8 +29,8 @@ logger = logging.getLogger(__name__) class YamlParser(object): - def __init__(self): - self.registry = ModuleRegistry() + def __init__(self, config=None): + self.registry = ModuleRegistry(config) self.data = {} self.jobs = [] @@ -150,9 +150,10 @@ class YamlParser(object): class ModuleRegistry(object): - def __init__(self): + def __init__(self, config): self.modules = [] self.handlers = {} + self.global_config = config for entrypoint in pkg_resources.iter_entry_points( group='jenkins_jobs.modules'): @@ -242,9 +243,11 @@ class Jenkins(object): class Builder(object): - def __init__(self, jenkins_url, jenkins_user, jenkins_password): + def __init__(self, jenkins_url, jenkins_user, jenkins_password, + config=None): self.jenkins = Jenkins(jenkins_url, jenkins_user, jenkins_password) self.cache = CacheStorage() + self.global_config = config def delete_job(self, name): self.jenkins.delete_job(name) @@ -258,7 +261,7 @@ class Builder(object): if (f.endswith('.yml') or f.endswith('.yaml'))] else: files_to_process = [fn] - parser = YamlParser() + parser = YamlParser(self.global_config) for in_file in files_to_process: logger.debug("Parsing YAML file {0}".format(in_file)) parser.parse(in_file) diff --git a/jenkins_jobs/modules/hipchat_notif.py b/jenkins_jobs/modules/hipchat_notif.py new file mode 100644 index 000000000..8f8caa313 --- /dev/null +++ b/jenkins_jobs/modules/hipchat_notif.py @@ -0,0 +1,106 @@ +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Enable hipchat notification of build execution. + +Example:: + + - job: + name: test_job + hipchat: + enabled: true + room: Testjob Build Notifications + start-notify: true + +In the jenkins UI specification, the hipchat plugin must be explicitly +selected as a publisher. This is not required (or supported) here - use the +``enabled`` parameter to enable/disable the publisher action. +If you set ``enabled: false``, no hipchat parameters are written to XML. +""" + +# Enabling hipchat notifications on a job requires specifying the hipchat +# config in job properties, and adding the hipchat notifier to the job's +# publishers list. +# The publisher configuration contains extra details not specified per job: +# - the hipchat authorisation token. +# - the jenkins server url. +# - a default room name/id. +# This complicates matters somewhat since the sensible place to store these +# details is in the global config file. +# The global config object is therefore passed down to the registry object, +# and this object is passed to the HipChat() class initialiser. + +import xml.etree.ElementTree as XML +import jenkins_jobs.modules.base +import jenkins_jobs.errors +import logging +import ConfigParser + +logger = logging.getLogger(__name__) + + +class HipChat(jenkins_jobs.modules.base.Base): + sequence = 80 + + def __init__(self, registry): + self.authToken = None + self.jenkinsUrl = None + self.registry = registry + + def _load_global_data(self): + """Load data from the global config object. + This is done lazily to avoid looking up the '[hipchat]' section + unless actually required. + """ + if(not self.authToken): + # Verify that the config object in the registry is of type + # ConfigParser (it could possibly be a regular 'dict' object which + # doesn't have the right get() method). + if(not isinstance(self.registry.global_config, + ConfigParser.ConfigParser)): + raise jenkins_jobs.errors.JenkinsJobsException( + 'HipChat requires a config object in the registry.') + self.authToken = self.registry.global_config.get( + 'hipchat', 'authtoken') + self.jenkinsUrl = self.registry.global_config.get('jenkins', 'url') + + def gen_xml(self, parser, xml_parent, data): + hipchat = data.get('hipchat') + if not hipchat or not hipchat.get('enabled', True): + return + if('room' not in hipchat): + raise jenkins_jobs.errors.YAMLFormatError( + "Missing hipchat 'room' specifier") + self._load_global_data() + + properties = xml_parent.find('properties') + if properties is None: + properties = XML.SubElement(xml_parent, 'properties') + pdefhip = XML.SubElement(properties, + 'jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty') + XML.SubElement(pdefhip, 'room').text = hipchat['room'] + XML.SubElement(pdefhip, 'startNotification').text = str( + hipchat.get('start-notify', 'false')).lower() + + publishers = xml_parent.find('publishers') + if publishers is None: + publishers = XML.SubElement(xml_parent, 'publishers') + hippub = XML.SubElement(publishers, + 'jenkins.plugins.hipchat.HipChatNotifier') + XML.SubElement(hippub, 'jenkinsUrl').text = self.jenkinsUrl + XML.SubElement(hippub, 'authToken').text = self.authToken + # The room specified here is the default room. The default is + # redundant in this case since a room must be specified. Leave empty. + XML.SubElement(hippub, 'room').text = '' diff --git a/setup.py b/setup.py index 2998b0da4..fea40f770 100644 --- a/setup.py +++ b/setup.py @@ -95,6 +95,7 @@ setup(name='jenkins_job_builder', 'triggers=jenkins_jobs.modules.triggers:Triggers', 'wrappers=jenkins_jobs.modules.wrappers:Wrappers', 'zuul=jenkins_jobs.modules.zuul:Zuul', + 'hipchat=jenkins_jobs.modules.hipchat_notif:HipChat', ] }