| # -*- coding: utf-8 -*- |
| |
| # Import Python libs |
| from __future__ import absolute_import |
| import difflib |
| import logging |
| |
| # Import Salt libs |
| import salt.ext.six as six |
| import salt.utils |
| |
| # Import XML parser |
| import xml.etree.ElementTree as ET |
| import hashlib |
| |
| # Jenkins |
| try: |
| import jenkins |
| HAS_JENKINS = True |
| except ImportError: |
| HAS_JENKINS = False |
| |
| log = logging.getLogger(__name__) |
| |
| |
| def __virtual__(): |
| ''' |
| Only load if jenkins_common module exist. |
| ''' |
| if not HAS_JENKINS: |
| return ( |
| False, |
| 'The jenkins_job state module cannot be loaded: ' |
| 'python Jenkins API client could not be imported') |
| if 'jenkins_common.call_groovy_script' not in __salt__: |
| return ( |
| False, |
| 'The jenkins_job state module cannot be loaded: ' |
| 'jenkins_common not found') |
| return True |
| |
| |
| def _elements_equal(e1, e2): |
| return hashlib.md5(e1).hexdigest() == hashlib.md5(e2).hexdigest() |
| |
| |
| def present(name, |
| config=None, |
| **kwargs): |
| ''' |
| Ensure the job is present in the Jenkins |
| configured jobs |
| name |
| The unique name for the Jenkins job |
| config |
| The Salt URL for the file to use for |
| configuring the job. |
| ''' |
| test = __opts__['test'] |
| ret = {'name': name, |
| 'result': True, |
| 'changes': {}, |
| 'comment': ['Job {0} is up to date.'.format(name)]} |
| if test: |
| status = 'CREATED' |
| ret['changes'][name] = status |
| ret['comment'] = 'Job %s %s' % (name, status.lower()) |
| else: |
| _current_job_config = '' |
| _job_exists = True |
| try: |
| _current_job_config = __salt__['jenkins.get_job_config'](name) |
| except salt.exceptions.SaltInvocationError as e: |
| if 'does not exists.' in str(e): |
| _job_exists = False |
| else: |
| raise e |
| |
| if _job_exists: |
| buf = six.moves.StringIO(_current_job_config) |
| oldXMLstring = buf.read() |
| |
| cached_source_path = __salt__['cp.cache_file'](config, __env__) |
| with salt.utils.fopen(cached_source_path) as _fp: |
| newXMLstring = _fp.read() |
| if not _elements_equal(oldXMLstring.strip(), newXMLstring.strip()): |
| oldXML = ET.fromstring(oldXMLstring) |
| newXML = ET.fromstring(newXMLstring) |
| diff = difflib.unified_diff( |
| ET.tostringlist(oldXML, encoding='utf8', method='xml'), |
| ET.tostringlist(newXML, encoding='utf8', method='xml'), lineterm='') |
| __salt__['jenkins.update_job'](name, config, __env__) |
| ret['changes'][name] = ''.join(diff) |
| ret['comment'].append('Job {0} updated.'.format(name)) |
| |
| else: |
| cached_source_path = __salt__['cp.cache_file'](config, __env__) |
| with salt.utils.fopen(cached_source_path) as _fp: |
| new_config_xml = _fp.read() |
| |
| __salt__['jenkins.create_job'](name, config, __env__) |
| |
| buf = six.moves.StringIO(new_config_xml) |
| diff = difflib.unified_diff('', buf.readlines(), lineterm='') |
| ret['changes'][name] = ''.join(diff) |
| ret['comment'].append('Job {0} added.'.format(name)) |
| |
| ret['comment'] = '\n'.join(ret['comment']) |
| return ret |
| |
| |
| def absent(name, |
| **kwargs): |
| ''' |
| Ensure the job is present in the Jenkins |
| configured jobs |
| |
| name |
| The name of the Jenkins job to remove. |
| |
| ''' |
| test = __opts__['test'] |
| ret = {'name': name, |
| 'result': True, |
| 'changes': {}, |
| 'comment': []} |
| if test: |
| status = 'DELETED' |
| ret['changes'][name] = status |
| ret['comment'] = 'Node %s %s' % (name, status.lower()) |
| else: |
| _job_exists = __salt__['jenkins.job_exists'](name) |
| |
| if _job_exists: |
| __salt__['jenkins.delete_job'](name) |
| ret['comment'] = 'Job {0} deleted.'.format(name) |
| else: |
| ret['comment'] = 'Job {0} already absent.'.format(name) |
| return ret |
| |
| |
| def cleanup(name, jobs, **kwargs): |
| ''' |
| Perform a cleanup - uninstall any installed job absents in given jobs list |
| |
| name |
| The name of the Jenkins job to remove. |
| jobs |
| List of jobs which may NOT be uninstalled |
| |
| ''' |
| test = __opts__['test'] |
| ret = {'name': name, |
| 'result': True, |
| 'changes': {}, |
| 'comment': "Cleanup not necessary"} |
| list_jobs_groovy = """\ |
| print(Jenkins.instance.items.collect{it -> it.name}) |
| """ |
| deleted_jobs = [] |
| if test: |
| status = 'CLEANED' |
| ret['changes'][name] = status |
| ret['comment'] = 'Jobs %s' % status.lower() |
| else: |
| call_result = __salt__['jenkins_common.call_groovy_script']( |
| list_jobs_groovy, {}) |
| if call_result["code"] == 200: |
| existing_jobs = call_result["msg"] |
| if existing_jobs: |
| for job in existing_jobs[1:-1].split(","): |
| if job: |
| job = job.strip() |
| if job not in jobs: |
| __salt__['jenkins.delete_job'](job) |
| deleted_jobs.append(job) |
| else: |
| log.error("Cannot get existing jobs list from Jenkins") |
| if len(deleted_jobs) > 0: |
| for del_job in deleted_jobs: |
| ret['changes'][del_job] = "removed" |
| ret['comment'] = 'Jobs {} deleted.'.format(deleted_jobs) |
| else: |
| status = 'FAILED' |
| log.error( |
| "Jenkins jobs API call failure: %s", call_result["msg"]) |
| ret['comment'] = 'Jenkins jobs API call failure: %s' % ( |
| call_result["msg"]) |
| return ret |