1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright: (c) Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = ''' 11--- 12module: jenkins_build 13short_description: Manage jenkins builds 14version_added: 2.2.0 15description: 16 - Manage Jenkins builds with Jenkins REST API. 17requirements: 18 - "python-jenkins >= 0.4.12" 19author: 20 - Brett Milford (@brettmilford) 21 - Tong He (@unnecessary-username) 22options: 23 args: 24 description: 25 - A list of parameters to pass to the build. 26 type: dict 27 name: 28 description: 29 - Name of the Jenkins job to build. 30 required: true 31 type: str 32 build_number: 33 description: 34 - An integer which specifies a build of a job. Is required to remove a build from the queue. 35 type: int 36 password: 37 description: 38 - Password to authenticate with the Jenkins server. 39 type: str 40 state: 41 description: 42 - Attribute that specifies if the build is to be created, deleted or stopped. 43 - The C(stopped) state has been added in community.general 3.3.0. 44 default: present 45 choices: ['present', 'absent', 'stopped'] 46 type: str 47 token: 48 description: 49 - API token used to authenticate with the Jenkins server. 50 type: str 51 url: 52 description: 53 - URL of the Jenkins server. 54 default: http://localhost:8080 55 type: str 56 user: 57 description: 58 - User to authenticate with the Jenkins server. 59 type: str 60''' 61 62EXAMPLES = ''' 63- name: Create a jenkins build using basic authentication 64 community.general.jenkins_build: 65 name: "test-check" 66 args: 67 cloud: "test" 68 availability_zone: "test_az" 69 state: present 70 user: admin 71 password: asdfg 72 url: http://localhost:8080 73 74- name: Stop a running jenkins build anonymously 75 community.general.jenkins_build: 76 name: "stop-check" 77 build_number: 3 78 state: stopped 79 url: http://localhost:8080 80 81- name: Delete a jenkins build using token authentication 82 community.general.jenkins_build: 83 name: "delete-experiment" 84 build_number: 30 85 state: absent 86 user: Jenkins 87 token: abcdefghijklmnopqrstuvwxyz123456 88 url: http://localhost:8080 89''' 90 91RETURN = ''' 92--- 93name: 94 description: Name of the jenkins job. 95 returned: success 96 type: str 97 sample: "test-job" 98state: 99 description: State of the jenkins job. 100 returned: success 101 type: str 102 sample: present 103user: 104 description: User used for authentication. 105 returned: success 106 type: str 107 sample: admin 108url: 109 description: Url to connect to the Jenkins server. 110 returned: success 111 type: str 112 sample: https://jenkins.mydomain.com 113build_info: 114 description: Build info of the jenkins job. 115 returned: success 116 type: dict 117''' 118 119import traceback 120from time import sleep 121 122JENKINS_IMP_ERR = None 123try: 124 import jenkins 125 python_jenkins_installed = True 126except ImportError: 127 JENKINS_IMP_ERR = traceback.format_exc() 128 python_jenkins_installed = False 129 130from ansible.module_utils.basic import AnsibleModule, missing_required_lib 131from ansible.module_utils.common.text.converters import to_native 132 133 134class JenkinsBuild: 135 136 def __init__(self, module): 137 self.module = module 138 139 self.name = module.params.get('name') 140 self.password = module.params.get('password') 141 self.args = module.params.get('args') 142 self.state = module.params.get('state') 143 self.token = module.params.get('token') 144 self.user = module.params.get('user') 145 self.jenkins_url = module.params.get('url') 146 self.build_number = module.params.get('build_number') 147 self.server = self.get_jenkins_connection() 148 149 self.result = { 150 'changed': False, 151 'url': self.jenkins_url, 152 'name': self.name, 153 'user': self.user, 154 'state': self.state, 155 } 156 157 self.EXCL_STATE = "excluded state" 158 159 def get_jenkins_connection(self): 160 try: 161 if (self.user and self.password): 162 return jenkins.Jenkins(self.jenkins_url, self.user, self.password) 163 elif (self.user and self.token): 164 return jenkins.Jenkins(self.jenkins_url, self.user, self.token) 165 elif (self.user and not (self.password or self.token)): 166 return jenkins.Jenkins(self.jenkins_url, self.user) 167 else: 168 return jenkins.Jenkins(self.jenkins_url) 169 except Exception as e: 170 self.module.fail_json(msg='Unable to connect to Jenkins server, %s' % to_native(e)) 171 172 def get_next_build(self): 173 try: 174 build_number = self.server.get_job_info(self.name)['nextBuildNumber'] 175 except Exception as e: 176 self.module.fail_json(msg='Unable to get job info from Jenkins server, %s' % to_native(e), 177 exception=traceback.format_exc()) 178 179 return build_number 180 181 def get_build_status(self): 182 try: 183 response = self.server.get_build_info(self.name, self.build_number) 184 return response 185 186 except Exception as e: 187 self.module.fail_json(msg='Unable to fetch build information, %s' % to_native(e), 188 exception=traceback.format_exc()) 189 190 def present_build(self): 191 self.build_number = self.get_next_build() 192 193 try: 194 if self.args is None: 195 self.server.build_job(self.name) 196 else: 197 self.server.build_job(self.name, self.args) 198 except Exception as e: 199 self.module.fail_json(msg='Unable to create build for %s: %s' % (self.jenkins_url, to_native(e)), 200 exception=traceback.format_exc()) 201 202 def stopped_build(self): 203 build_info = None 204 try: 205 build_info = self.server.get_build_info(self.name, self.build_number) 206 if build_info['building'] is True: 207 self.server.stop_build(self.name, self.build_number) 208 except Exception as e: 209 self.module.fail_json(msg='Unable to stop build for %s: %s' % (self.jenkins_url, to_native(e)), 210 exception=traceback.format_exc()) 211 else: 212 if build_info['building'] is False: 213 self.module.exit_json(**self.result) 214 215 def absent_build(self): 216 try: 217 self.server.delete_build(self.name, self.build_number) 218 except Exception as e: 219 self.module.fail_json(msg='Unable to delete build for %s: %s' % (self.jenkins_url, to_native(e)), 220 exception=traceback.format_exc()) 221 222 def get_result(self): 223 result = self.result 224 build_status = self.get_build_status() 225 226 if build_status['result'] is None: 227 sleep(10) 228 self.get_result() 229 else: 230 if self.state == "stopped" and build_status['result'] == "ABORTED": 231 result['changed'] = True 232 result['build_info'] = build_status 233 elif build_status['result'] == "SUCCESS": 234 result['changed'] = True 235 result['build_info'] = build_status 236 else: 237 result['failed'] = True 238 result['build_info'] = build_status 239 240 return result 241 242 243def test_dependencies(module): 244 if not python_jenkins_installed: 245 module.fail_json( 246 msg=missing_required_lib("python-jenkins", 247 url="https://python-jenkins.readthedocs.io/en/latest/install.html"), 248 exception=JENKINS_IMP_ERR) 249 250 251def main(): 252 module = AnsibleModule( 253 argument_spec=dict( 254 args=dict(type='dict'), 255 build_number=dict(type='int'), 256 name=dict(required=True), 257 password=dict(no_log=True), 258 state=dict(choices=['present', 'absent', 'stopped'], default="present"), 259 token=dict(no_log=True), 260 url=dict(default="http://localhost:8080"), 261 user=dict(), 262 ), 263 mutually_exclusive=[['password', 'token']], 264 required_if=[['state', 'absent', ['build_number'], True], ['state', 'stopped', ['build_number'], True]], 265 ) 266 267 test_dependencies(module) 268 jenkins_build = JenkinsBuild(module) 269 270 if module.params.get('state') == "present": 271 jenkins_build.present_build() 272 elif module.params.get('state') == "stopped": 273 jenkins_build.stopped_build() 274 else: 275 jenkins_build.absent_build() 276 277 sleep(10) 278 result = jenkins_build.get_result() 279 module.exit_json(**result) 280 281 282if __name__ == '__main__': 283 main() 284