#!/usr/bin/env python # Copyright (C) 2012 OpenStack, LLC. # # 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. # Manage jobs in Jenkins server import os import hashlib import yaml import xml.etree.ElementTree as XML from xml.dom import minidom import jenkins import re import pkg_resources class YamlParser(object): def __init__(self): self.registry = ModuleRegistry() self.data = {} self.jobs = [] def parse(self, fn): data = yaml.load(open(fn)) for item in data: cls, dfn = item.items()[0] group = self.data.get(cls, {}) name = dfn['name'] group[name] = dfn self.data[cls] = group def getJob(self, name): job = self.data.get('job', {}).get(name, None) if not job: return job return self.applyDefaults(job) def getJobGroup(self, name): return self.data.get('job-group', {}).get(name, None) def getJobTemplate(self, name): job = self.data.get('job-template', {}).get(name, None) if not job: return job return self.applyDefaults(job) def applyDefaults(self, data): whichdefaults = data.get('defaults', 'global') defaults = self.data.get('defaults', {}).get(whichdefaults, {}) newdata = {} newdata.update(defaults) newdata.update(data) return newdata def generateXML(self): 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(): job = self.applyDefaults(job) self.getXMLForJob(job) for project in self.data.get('project', {}).values(): for jobname in project.get('jobs', []): 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_jobname in group['jobs']: 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(group) # Except name, since the group's name is not useful d['name'] = project['name'] if template: self.getXMLForTemplateJob(d, template) continue # see if it's a template template = self.getJobTemplate(jobname) if template: self.getXMLForTemplateJob(project, template) def getXMLForTemplateJob(self, project, template): s = yaml.dump(template, default_flow_style=False) s = s.format(**project) data = yaml.load(s) self.getXMLForJob(data) def getXMLForJob(self, data): kind = data.get('project-type', 'freestyle') for ep in pkg_resources.iter_entry_points( group='jenkins_jobs.projects', name=kind): Mod = ep.load() mod = Mod(self.registry) xml = mod.root_xml(data) self.gen_xml(xml, data) job = XmlJob(xml, data['name']) self.jobs.append(job) break def gen_xml(self, xml, data): XML.SubElement(xml, 'actions') description = XML.SubElement(xml, 'description') description.text = data.get('description', '') XML.SubElement(xml, 'keepDependencies').text = 'false' if data.get('disabled'): XML.SubElement(xml, 'disabled').text = 'true' else: XML.SubElement(xml, 'disabled').text = 'false' XML.SubElement(xml, 'blockBuildWhenDownstreamBuilding').text = 'false' XML.SubElement(xml, 'blockBuildWhenUpstreamBuilding').text = 'false' if data.get('concurrent'): XML.SubElement(xml, 'concurrentBuild').text = 'true' else: XML.SubElement(xml, 'concurrentBuild').text = 'false' if('quiet-period' in data): XML.SubElement(xml, 'quietPeriod').text = str(data['quiet-period']) for module in self.registry.modules: if hasattr(module, 'gen_xml'): module.gen_xml(self, xml, data) class ModuleRegistry(object): def __init__(self): self.modules = [] self.handlers = {} for entrypoint in pkg_resources.iter_entry_points( group='jenkins_jobs.modules'): Mod = entrypoint.load() mod = Mod(self) self.modules.append(mod) self.modules.sort(lambda a, b: cmp(a.sequence, b.sequence)) def registerHandler(self, category, name, method): cat_dict = self.handlers.get(category, {}) if not cat_dict: self.handlers[category] = cat_dict cat_dict[name] = method def getHandler(self, category, name): return self.handlers[category][name] class XmlJob(object): def __init__(self, xml, name): self.xml = xml self.name = name def md5(self): return hashlib.md5(self.output()).hexdigest() # Pretty printing ideas from # http://stackoverflow.com/questions/749796/pretty-printing-xml-in-python pretty_text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+\g<1>