Start using entry-points.

Change-Id: Idcfddb3b43b6cfef4b20919a84540706d7a0a0b1
This commit is contained in:
James E. Blair 2012-08-07 14:11:29 -07:00
parent ae673c9ac2
commit 8ceccff811
15 changed files with 108 additions and 376 deletions

View File

@ -1,279 +0,0 @@
#! /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 argparse
import hashlib
import yaml
import xml.etree.ElementTree as XML
from xml.dom import minidom
import jenkins
import ConfigParser
from StringIO import StringIO
import re
import pkgutil
import modules
class JenkinsJobsException(Exception): pass
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('file', help='YAML file for update')
parser_update = subparser.add_parser('test')
parser_update.add_argument('file', help='YAML file for test')
parser_delete = subparser.add_parser('delete')
parser_delete.add_argument('name', help='name of job')
parser.add_argument('--conf', dest='conf', help='Configuration file')
options = parser.parse_args()
if options.conf:
conf = options.conf
else:
conf = 'jenkins_jobs.ini'
if not options.command == 'test':
conffp = open(conf, 'r')
config = ConfigParser.ConfigParser()
config.readfp(conffp)
class YamlParser(object):
def __init__(self, yfile):
self.registry = ModuleRegistry()
self.data = yaml.load_all(yfile)
self.it = self.data.__iter__()
self.job_name = None
self.template_data = None
self.current = None
self.current_template = None
self.template_it = None
self.reading_template = False
self.eof = False
self.seek_next_xml()
def process_template(self):
project_data = self.current['project']
template_file = file('templates/' + project_data['template'] + '.yml', 'r')
template = template_file.read()
template_file.close()
values = self.current['values'].iteritems()
for key, value in values:
key = '@' + key.upper() + '@'
template = template.replace(key, value)
template_steam = StringIO(template)
self.template_data = yaml.load_all(template_steam)
self.template_it = self.template_data.__iter__()
self.reading_template = True
def get_next_xml(self):
if not self.eof:
if self.reading_template:
data = XmlParser(self.current_template, self.registry)
self.job_name = self.current_template['main']['name']
else:
data = XmlParser(self.current, self.registry)
self.job_name = self.current['main']['name']
self.seek_next_xml()
return data
else:
raise JenkinsJobsException('End of file')
def seek_next_xml(self):
if self.reading_template:
try:
self.current_template = self.template_it.next()
return
except StopIteration:
self.reading_template = False
try:
self.current = self.it.next()
except StopIteration:
self.eof = True
if self.current.has_key('project'):
self.process_template()
self.current_template = self.template_it.next()
def get_name(self):
return self.job_name
class ModuleRegistry(object):
# TODO: make this extensible
def __init__(self):
self.modules = []
self.handlers = {}
for importer, modname, ispkg in pkgutil.iter_modules(modules.__path__):
module = __import__('modules.'+modname, fromlist=['register'])
register = getattr(module, 'register', None)
if register:
register(self)
def registerModule(self, mod):
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 XmlParser(object):
def __init__(self, data, registry):
self.data = data
self.registry = registry
self._build()
def _build(self):
for module in self.registry.modules:
if hasattr(module, 'root_xml'):
element = module.root_xml(self.data)
if element is not None:
self.xml = element
for module in self.registry.modules:
if hasattr(module, 'handle_data'):
module.handle_data(self.data)
XML.SubElement(self.xml, 'actions')
description = XML.SubElement(self.xml, 'description')
description.text = "THIS JOB IS MANAGED BY PUPPET AND WILL BE OVERWRITTEN.\n\n\
DON'T EDIT THIS JOB THROUGH THE WEB\n\n\
If you would like to make changes to this job, please see:\n\n\
https://github.com/openstack/openstack-ci-puppet\n\n\
In modules/jenkins_jobs"
XML.SubElement(self.xml, 'keepDependencies').text = 'false'
if self.data['main'].get('disabled'):
XML.SubElement(self.xml, 'disabled').text = 'true'
else:
XML.SubElement(self.xml, 'disabled').text = 'false'
XML.SubElement(self.xml, 'blockBuildWhenDownstreamBuilding').text = 'false'
XML.SubElement(self.xml, 'blockBuildWhenUpstreamBuilding').text = 'false'
if self.data['main'].get('concurrent'):
XML.SubElement(self.xml, 'concurrentBuild').text = 'true'
else:
XML.SubElement(self.xml, 'concurrentBuild').text = 'false'
for module in self.registry.modules:
if hasattr(module, 'gen_xml'):
module.gen_xml(self.xml, self.data)
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+</', re.DOTALL)
def output(self):
out = minidom.parseString(XML.tostring(self.xml)).toprettyxml(indent=' ')
return self.pretty_text_re.sub('>\g<1></', out)
class CacheStorage(object):
def __init__(self):
self.cachefilename = os.path.expanduser('~/.jenkins_jobs_cache.yml')
try:
yfile = file(self.cachefilename, 'r')
except IOError:
self.data = {}
return
self.data = yaml.load(yfile)
yfile.close()
def set(self, job, md5):
self.data[job] = md5
yfile = file(self.cachefilename, 'w')
yaml.dump(self.data, yfile)
yfile.close()
def is_cached(self, job):
if self.data.has_key(job):
return True
return False
def has_changed(self, job, md5):
if self.data.has_key(job) and self.data[job] == md5:
return False
return True
class Jenkins(object):
def __init__(self, url, user, password):
self.jenkins = jenkins.Jenkins(url, user, password)
def update_job(self, job_name, xml):
if self.is_job(job_name):
self.jenkins.reconfig_job(job_name, xml)
else:
self.jenkins.create_job(job_name, xml)
def is_job(self, job_name):
return self.jenkins.job_exists(job_name)
def get_job_md5(self, job_name):
xml = self.jenkins.get_job_config(job_name)
return hashlib.md5(xml).hexdigest()
def delete_job(self, job_name):
if self.is_job(job_name):
self.jenkins.delete_job(job_name)
def delete_job():
remote_jenkins = Jenkins(config.get('jenkins','url'), config.get('jenkins','user'), config.get('jenkins','password'))
remote_jenkins.delete_job(options.name)
def update_job(test = False):
if os.path.isdir(options.file):
files_to_process = [os.path.join(options.file, f)
for f in os.listdir(options.file)]
else:
files_to_process = [options.file]
cache = CacheStorage()
if not test:
remote_jenkins = Jenkins(config.get('jenkins','url'), config.get('jenkins','user'), config.get('jenkins','password'))
for in_file in files_to_process:
yparse = YamlParser(open(in_file, 'r'))
while True:
try:
xml = yparse.get_next_xml()
job = yparse.get_name()
if test:
print xml.output()
continue
md5 = xml.md5()
if remote_jenkins.is_job(job) and not cache.is_cached(job):
old_md5 = remote_jenkins.get_job_md5(job)
cache.set(job, old_md5)
if cache.has_changed(job, md5):
remote_jenkins.update_job(job, xml.output())
cache.set(job, md5)
except JenkinsJobsException:
break
if options.command == 'delete':
delete_job()
elif options.command == 'update':
update_job()
elif options.command == 'test':
update_job(True)

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -19,18 +18,13 @@
# - node: 'oneiric'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = AssignedNode()
registry.registerModule(mod)
class AssignedNode(object):
class AssignedNode(jenkins_jobs.modules.base.Base):
sequence = 40
def gen_xml(self, xml_parent, data):
node = data['assignednode']['node']
XML.SubElement(xml_parent, 'assignedNode').text = node
XML.SubElement(xml_parent, 'canRoam').text = 'false'
XML.SubElement(xml_parent, 'canRoam').text = 'false'

View File

@ -0,0 +1,22 @@
# 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.
# Base class for a jenkins_jobs module
class Base(object):
sequence = 10
def __init__(self, registry):
self.registry = registry

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -20,18 +19,14 @@
# - 'python26'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Builders(registry)
registry.registerModule(mod)
class Builders(object):
class Builders(jenkins_jobs.modules.base.Base):
sequence = 60
def __init__(self, registry):
self.registry = registry
super(Builders, self).__init__(registry)
for f in dir(self):
if not f.startswith('_builder_'):
continue

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -22,14 +21,10 @@
# artifactNumToKeep: -1
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = LogRotate()
registry.registerModule(mod)
class LogRotate(object):
class LogRotate(jenkins_jobs.modules.base.Base):
sequence = 10
def handle_data(self, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -22,14 +21,10 @@
# goals: 'test'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Freestyle()
registry.registerModule(mod)
class Freestyle(object):
class Freestyle(jenkins_jobs.modules.base.Base):
sequence = 0
def root_xml(self, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -22,14 +21,9 @@
# goals: 'test'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Maven()
registry.registerModule(mod)
class Maven(object):
class Maven(jenkins_jobs.modules.base.Base):
sequence = 0
def root_xml(self, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -17,14 +16,10 @@
# No additional YAML needed
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Properties()
registry.registerModule(mod)
class Properties(object):
class Properties(jenkins_jobs.modules.base.Base):
sequence = 20
def handle_data(self, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -17,18 +16,13 @@
# No additional YAML needed
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Publishers(registry)
registry.registerModule(mod)
class Publishers(object):
class Publishers(jenkins_jobs.modules.base.Base):
sequence = 70
def __init__(self, registry):
self.registry = registry
super(Publishers, self).__init__(registry)
for f in dir(self):
if not f.startswith('_publisher_'):
continue

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -21,14 +20,10 @@
# scm: 'false'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = SCM()
registry.registerModule(mod)
class SCM(object):
class SCM(jenkins_jobs.modules.base.Base):
sequence = 30
def handle_data(self, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -40,18 +39,13 @@
# triggerApprovalCategory and triggerApprovalValue only required if triggerOnCommentAddedEvent: 'true'
import xml.etree.ElementTree as XML
import jenkins_jobs.modules.base
def register(registry):
mod = Triggers(registry)
registry.registerModule(mod)
class Triggers(object):
class Triggers(jenkins_jobs.modules.base.Base):
sequence = 50
def __init__(self, registry):
self.registry = registry
super(Triggers, self).__init__(registry)
for f in dir(self):
if not f.startswith('_trigger_'):
continue

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -16,13 +15,10 @@
# Jenkins Job module for wrappers
import xml.etree.ElementTree as XML
def register(registry):
mod = Wrappers()
registry.registerModule(mod)
import jenkins_jobs.modules.base
class Wrappers(object):
class Wrappers(jenkins_jobs.modules.base.Base):
sequence = 80
def gen_xml(self, xml_parent, data):

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python
# Copyright (C) 2012 OpenStack, LLC.
# 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.
@ -15,6 +14,9 @@
# Jenkins Job module for Zuul
import jenkins_jobs.modules.base
ZUUL_PARAMETERS = [
{'description': 'Zuul provided key to link builds with Gerrit events',
'name': 'UUID',
@ -53,13 +55,7 @@ ZUUL_NOTIFICATIONS = [
'protocol': 'HTTP'}
]
def register(registry):
mod = Zuul()
registry.registerModule(mod)
class Zuul(object):
class Zuul(jenkins_jobs.modules.base.Base):
sequence = 0
def handle_data(self, data):

46
setup.py Normal file
View File

@ -0,0 +1,46 @@
# 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.
from setuptools import find_packages
from setuptools import setup
setup(name='jenkins_job_builder',
version='0.1',
description="Manage Jenkins jobs with YAML",
license='Apache License (2.0)',
author='Hewlett-Packard Development Company, L.P.',
author_email='openstack@lists.launchpad.net',
scripts=['jenkins-jobs'],
include_package_data=True,
zip_safe=False,
packages=find_packages(),
entry_points = {
'jenkins_jobs.modules': [
'freestyle=jenkins_jobs.modules.project_freestyle:Freestyle',
'maven=jenkins_jobs.modules.project_maven:Maven',
'assignednode=jenkins_jobs.modules.assignednode:AssignedNode',
'builders=jenkins_jobs.modules.builders:Builders',
'logrotate=jenkins_jobs.modules.logrotate:LogRotate',
'properties=jenkins_jobs.modules.properties:Properties',
'publishers=jenkins_jobs.modules.publishers:Publishers',
'scm=jenkins_jobs.modules.scm:SCM',
'triggers=jenkins_jobs.modules.triggers:Triggers',
'wrappers=jenkins_jobs.modules.wrappers:Wrappers',
'zuul=jenkins_jobs.modules.zuul:Zuul',
]
}
)