Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | # Import Python libs |
| 4 | from __future__ import absolute_import |
| 5 | import difflib |
| 6 | import logging |
| 7 | |
| 8 | # Import Salt libs |
| 9 | import salt.ext.six as six |
| 10 | import salt.utils |
| 11 | |
| 12 | # Import XML parser |
| 13 | import xml.etree.ElementTree as ET |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 14 | import hashlib |
| 15 | |
| 16 | # Jenkins |
| 17 | try: |
Adam Tengler | 70763e0 | 2017-08-21 16:50:32 +0000 | [diff] [blame] | 18 | import jenkins |
| 19 | HAS_JENKINS = True |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 20 | except ImportError: |
Adam Tengler | 70763e0 | 2017-08-21 16:50:32 +0000 | [diff] [blame] | 21 | HAS_JENKINS = False |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 22 | |
| 23 | log = logging.getLogger(__name__) |
| 24 | |
| 25 | |
Ilya Kharin | 3d8bffe | 2017-06-22 17:40:31 +0400 | [diff] [blame] | 26 | def __virtual__(): |
| 27 | ''' |
| 28 | Only load if jenkins_common module exist. |
| 29 | ''' |
Adam Tengler | 70763e0 | 2017-08-21 16:50:32 +0000 | [diff] [blame] | 30 | if not HAS_JENKINS: |
| 31 | return ( |
| 32 | False, |
| 33 | 'The jenkins_job state module cannot be loaded: ' |
| 34 | 'python Jenkins API client could not be imported') |
| 35 | if 'jenkins_common.call_groovy_script' not in __salt__: |
Ilya Kharin | 3d8bffe | 2017-06-22 17:40:31 +0400 | [diff] [blame] | 36 | return ( |
| 37 | False, |
| 38 | 'The jenkins_job state module cannot be loaded: ' |
| 39 | 'jenkins_common not found') |
| 40 | return True |
| 41 | |
| 42 | |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 43 | def _elements_equal(e1, e2): |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 44 | return hashlib.md5(e1).hexdigest() == hashlib.md5(e2).hexdigest() |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 45 | |
| 46 | |
| 47 | def present(name, |
| 48 | config=None, |
| 49 | **kwargs): |
| 50 | ''' |
| 51 | Ensure the job is present in the Jenkins |
| 52 | configured jobs |
| 53 | name |
| 54 | The unique name for the Jenkins job |
| 55 | config |
| 56 | The Salt URL for the file to use for |
| 57 | configuring the job. |
| 58 | ''' |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 59 | test = __opts__['test'] |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 60 | ret = {'name': name, |
| 61 | 'result': True, |
| 62 | 'changes': {}, |
| 63 | 'comment': ['Job {0} is up to date.'.format(name)]} |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 64 | if test: |
| 65 | status = 'CREATED' |
| 66 | ret['changes'][name] = status |
| 67 | ret['comment'] = 'Job %s %s' % (name, status.lower()) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 68 | else: |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 69 | _current_job_config = '' |
| 70 | _job_exists = True |
| 71 | try: |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 72 | _current_job_config = __salt__['jenkins.get_job_config'](name) |
Filip Pytloun | ce9cae6 | 2017-12-03 15:42:25 +0100 | [diff] [blame] | 73 | except Exception as e: |
| 74 | if 'does not exist' in str(e): |
chnyda | 2d78731 | 2017-08-07 20:07:56 +0200 | [diff] [blame] | 75 | _job_exists = False |
| 76 | else: |
| 77 | raise e |
| 78 | |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 79 | if _job_exists: |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 80 | buf = six.moves.StringIO(_current_job_config) |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 81 | oldXMLstring = buf.read() |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 82 | |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 83 | cached_source_path = __salt__['cp.cache_file'](config, __env__) |
| 84 | with salt.utils.fopen(cached_source_path) as _fp: |
chnyda | 90f133f | 2017-08-02 10:46:13 +0200 | [diff] [blame] | 85 | newXMLstring = _fp.read() |
| 86 | if not _elements_equal(oldXMLstring.strip(), newXMLstring.strip()): |
| 87 | oldXML = ET.fromstring(oldXMLstring) |
| 88 | newXML = ET.fromstring(newXMLstring) |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 89 | diff = difflib.unified_diff( |
| 90 | ET.tostringlist(oldXML, encoding='utf8', method='xml'), |
| 91 | ET.tostringlist(newXML, encoding='utf8', method='xml'), lineterm='') |
| 92 | __salt__['jenkins.update_job'](name, config, __env__) |
Jakub Josef | 654a148 | 2017-01-26 17:41:16 +0100 | [diff] [blame] | 93 | ret['changes'][name] = ''.join(diff) |
chnyda | ea19043 | 2017-08-08 15:37:29 +0200 | [diff] [blame] | 94 | ret['comment'].append('Job {0} updated.'.format(name)) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 95 | |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 96 | else: |
| 97 | cached_source_path = __salt__['cp.cache_file'](config, __env__) |
| 98 | with salt.utils.fopen(cached_source_path) as _fp: |
| 99 | new_config_xml = _fp.read() |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 100 | |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 101 | __salt__['jenkins.create_job'](name, config, __env__) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 102 | |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 103 | buf = six.moves.StringIO(new_config_xml) |
Jakub Josef | 654a148 | 2017-01-26 17:41:16 +0100 | [diff] [blame] | 104 | diff = difflib.unified_diff('', buf.readlines(), lineterm='') |
| 105 | ret['changes'][name] = ''.join(diff) |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 106 | ret['comment'].append('Job {0} added.'.format(name)) |
| 107 | |
| 108 | ret['comment'] = '\n'.join(ret['comment']) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 109 | return ret |
| 110 | |
| 111 | |
| 112 | def absent(name, |
| 113 | **kwargs): |
| 114 | ''' |
| 115 | Ensure the job is present in the Jenkins |
| 116 | configured jobs |
| 117 | |
| 118 | name |
| 119 | The name of the Jenkins job to remove. |
| 120 | |
| 121 | ''' |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 122 | test = __opts__['test'] |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 123 | ret = {'name': name, |
| 124 | 'result': True, |
| 125 | 'changes': {}, |
| 126 | 'comment': []} |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 127 | if test: |
| 128 | status = 'DELETED' |
| 129 | ret['changes'][name] = status |
| 130 | ret['comment'] = 'Node %s %s' % (name, status.lower()) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 131 | else: |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 132 | _job_exists = __salt__['jenkins.job_exists'](name) |
| 133 | |
| 134 | if _job_exists: |
| 135 | __salt__['jenkins.delete_job'](name) |
| 136 | ret['comment'] = 'Job {0} deleted.'.format(name) |
| 137 | else: |
| 138 | ret['comment'] = 'Job {0} already absent.'.format(name) |
| 139 | return ret |
| 140 | |
| 141 | |
| 142 | def cleanup(name, jobs, **kwargs): |
| 143 | ''' |
| 144 | Perform a cleanup - uninstall any installed job absents in given jobs list |
| 145 | |
| 146 | name |
| 147 | The name of the Jenkins job to remove. |
| 148 | jobs |
| 149 | List of jobs which may NOT be uninstalled |
| 150 | |
| 151 | ''' |
| 152 | test = __opts__['test'] |
| 153 | ret = {'name': name, |
| 154 | 'result': True, |
| 155 | 'changes': {}, |
| 156 | 'comment': "Cleanup not necessary"} |
| 157 | list_jobs_groovy = """\ |
Adam Tengler | 70763e0 | 2017-08-21 16:50:32 +0000 | [diff] [blame] | 158 | print(Jenkins.instance.items.collect{it -> it.name}) |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 159 | """ |
| 160 | deleted_jobs = [] |
| 161 | if test: |
| 162 | status = 'CLEANED' |
| 163 | ret['changes'][name] = status |
| 164 | ret['comment'] = 'Jobs %s' % status.lower() |
| 165 | else: |
Adam Tengler | 70763e0 | 2017-08-21 16:50:32 +0000 | [diff] [blame] | 166 | call_result = __salt__['jenkins_common.call_groovy_script']( |
| 167 | list_jobs_groovy, {}) |
Jakub Josef | 2a7739b | 2017-01-24 18:33:44 +0100 | [diff] [blame] | 168 | if call_result["code"] == 200: |
| 169 | existing_jobs = call_result["msg"] |
| 170 | if existing_jobs: |
| 171 | for job in existing_jobs[1:-1].split(","): |
| 172 | if job: |
| 173 | job = job.strip() |
| 174 | if job not in jobs: |
| 175 | __salt__['jenkins.delete_job'](job) |
| 176 | deleted_jobs.append(job) |
| 177 | else: |
| 178 | log.error("Cannot get existing jobs list from Jenkins") |
| 179 | if len(deleted_jobs) > 0: |
| 180 | for del_job in deleted_jobs: |
| 181 | ret['changes'][del_job] = "removed" |
| 182 | ret['comment'] = 'Jobs {} deleted.'.format(deleted_jobs) |
| 183 | else: |
| 184 | status = 'FAILED' |
| 185 | log.error( |
| 186 | "Jenkins jobs API call failure: %s", call_result["msg"]) |
| 187 | ret['comment'] = 'Jenkins jobs API call failure: %s' % ( |
| 188 | call_result["msg"]) |
Jakub Josef | e380798 | 2016-12-15 11:54:51 +0100 | [diff] [blame] | 189 | return ret |