blob: 64cede66f7f23ab25bcd37b1ecc354e8d930d356 [file] [log] [blame]
Jakub Josef8e7385e2016-12-07 21:20:34 +01001import logging
Ilya Kharin3d8bffe2017-06-22 17:40:31 +04002
Jakub Josef8e7385e2016-12-07 21:20:34 +01003from salt.exceptions import SaltInvocationError
Adam Tengler70763e02017-08-21 16:50:32 +00004from string import Template
Jakub Josef8e7385e2016-12-07 21:20:34 +01005
Ilya Kharin3d8bffe2017-06-22 17:40:31 +04006try:
7 import bcrypt
8 HAS_BCRYPT = True
9except ImportError:
10 HAS_BCRYPT = False
11
12try:
13 import requests
14 HAS_REQUESTS = True
15except ImportError:
16 HAS_REQUESTS = False
17
Jakub Josef8e7385e2016-12-07 21:20:34 +010018logger = logging.getLogger(__name__)
19
20
Ilya Kharin3d8bffe2017-06-22 17:40:31 +040021def __virtual__():
22 '''
23 Only load if bcrypt and requests libraries exist.
24 '''
25 if not HAS_BCRYPT:
26 return (
27 False,
28 'Can not load module jenkins_common: bcrypt library not found')
29 if not HAS_REQUESTS:
30 return (
31 False,
32 'Can not load module jenkins_common: requests library not found')
33 return True
34
35
Adam Tengler70763e02017-08-21 16:50:32 +000036def call_groovy_script(script, props, username=None,
37 password=None, success_status_codes=[200]):
Jakub Josef8e7385e2016-12-07 21:20:34 +010038 """
39 Common method for call Jenkins groovy script API
40
Jakub Josef7ae6b242016-12-14 14:41:44 +010041 :param script: groovy script template
42 :param props: groovy script properties
43 :param username: jenkins username (optional,
44 if missing creds from sall will be used)
45 :param password: jenkins password (optional,
46 if missing creds from sall will be used)
47 :param success_status_codes: success response status code
48 (optional) in some cases we want to declare error call as success
Jakub Josef8e7385e2016-12-07 21:20:34 +010049 :returns: HTTP dict {status,code,msg}
50 """
51 ret = {
52 "status": "FAILED",
53 "code": 999,
54 "msg": ""
55 }
56 jenkins_url, jenkins_user, jenkins_password = get_jenkins_auth()
Jakub Josef7ae6b242016-12-14 14:41:44 +010057 if username:
58 jenkins_user = username
59 if password:
60 jenkins_password = password
61
Jakub Josef8e7385e2016-12-07 21:20:34 +010062 if not jenkins_url:
63 raise SaltInvocationError('No Jenkins URL found.')
Jakub Josefe13e2e72016-12-08 13:41:19 +010064
Ivan Berezovskiy29849dd2020-03-25 20:13:08 +040065 token_obj, req_cookies = get_api_crumb(jenkins_url, jenkins_user, jenkins_password)
Jakub Josefe13e2e72016-12-08 13:41:19 +010066 req_data = {"script": render_groovy_script(script, props)}
67 if token_obj:
68 req_data[token_obj["crumbRequestField"]] = token_obj["crumb"]
69
70 logger.debug("Calling Jenkins script API with URL: %s", jenkins_url)
71 req = requests.post('%s/scriptText' % jenkins_url,
Filip Pytloun0c7d9052018-04-05 17:58:39 +020072 auth=(jenkins_user, jenkins_password) if jenkins_user else None,
Ivan Berezovskiy29849dd2020-03-25 20:13:08 +040073 data=req_data, cookies=req_cookies)
Jakub Josefe13e2e72016-12-08 13:41:19 +010074 ret["code"] = req.status_code
Jakub Josef063a7532017-01-11 15:48:01 +010075 ret["msg"] = req.text
Jakub Josef7ae6b242016-12-14 14:41:44 +010076 if req.status_code in success_status_codes:
Jakub Josefe13e2e72016-12-08 13:41:19 +010077 ret["status"] = "SUCCESS"
Jakub Josefe13e2e72016-12-08 13:41:19 +010078 logger.debug("Jenkins script API call success: %s", ret)
Jakub Josef8e7385e2016-12-07 21:20:34 +010079 else:
Jakub Josefe13e2e72016-12-08 13:41:19 +010080 logger.error("Jenkins script API call failed. \
81 Return code %s. Text: %s", req.status_code, req.text)
Jakub Josef8e7385e2016-12-07 21:20:34 +010082 return ret
83
84
Adam Tengler70763e02017-08-21 16:50:32 +000085def render_groovy_script(script_template, props):
Jakub Josef8e7385e2016-12-07 21:20:34 +010086 """
87 Helper method for rendering groovy script with props
88
Adam Tengler70763e02017-08-21 16:50:32 +000089 :param script_template: groovy script template
Jakub Josef3de91af2016-12-08 17:03:33 +010090 :param props: groovy script properties
Jakub Josef8e7385e2016-12-07 21:20:34 +010091 :returns: generated groovy script
92 """
Adam Tengler70763e02017-08-21 16:50:32 +000093 template = Template(script_template)
94 return template.safe_substitute(props)
Jakub Josef8e7385e2016-12-07 21:20:34 +010095
96
97def get_api_crumb(jenkins_url=None, jenkins_user=None, jenkins_password=None):
98 """
99 Obtains Jenkins API crumb, if CSRF protection is enabled.
100 Jenkins params can be given by params or not, if not,
101 params will be get from salt.
102
103 :param jenkins_url: Jenkins URL (optional)
104 :param jenkins_user: Jenkins admin username (optional)
105 :param jenkins_password: Jenkins admin password (optional)
106 :returns: salt-specified state dict
107 """
108 if not jenkins_url:
109 jenkins_url, jenkins_user, jenkins_password = get_jenkins_auth()
110 logger.debug("Obtaining Jenkins API crumb for URL: %s", jenkins_url)
111 tokenReq = requests.get("%s/crumbIssuer/api/json" % jenkins_url,
Jakub Josefe13e2e72016-12-08 13:41:19 +0100112 auth=(jenkins_user, jenkins_password) if jenkins_user else None)
Jakub Josef8e7385e2016-12-07 21:20:34 +0100113 if tokenReq.status_code == 200:
Alexander Evseevcd836a12018-07-27 13:02:45 +0200114 logger.debug("Got Jenkins API crumb: %s", tokenReq.json())
Ivan Berezovskiy29849dd2020-03-25 20:13:08 +0400115 return tokenReq.json(), tokenReq.cookies
Alexander Evseevcd836a12018-07-27 13:02:45 +0200116 elif tokenReq.status_code in [404, 401, 502, 503]:
Jakub Josef7ae6b242016-12-14 14:41:44 +0100117 # 404 means CSRF security is disabled, so api crumb is not necessary,
118 # 401 means unauthorized
Alexander Evseevcd836a12018-07-27 13:02:45 +0200119 # 50x means jenkins is unavailabe - fail in call_groovy_script, but
120 # not here, to handle exception in state
121 logger.debug("Got error %s: %s", str(tokenReq.status_code), tokenReq.reason)
Ivan Berezovskiy29849dd2020-03-25 20:13:08 +0400122 return None, None
Jakub Josef8e7385e2016-12-07 21:20:34 +0100123 else:
Jakub Josefe13e2e72016-12-08 13:41:19 +0100124 raise Exception("Cannot obtain Jenkins API crumb. Status code: %s. Text: %s" %
Jakub Josef3de91af2016-12-08 17:03:33 +0100125 (tokenReq.status_code, tokenReq.text))
Jakub Josef8e7385e2016-12-07 21:20:34 +0100126
127
Jakub Josef8e7385e2016-12-07 21:20:34 +0100128def get_jenkins_auth():
129 """
130 Get jenkins params from salt
131 """
132 jenkins_url = __salt__['config.get']('jenkins.url') or \
133 __salt__['config.get']('jenkins:url') or \
134 __salt__['pillar.get']('jenkins.url')
135
136 jenkins_user = __salt__['config.get']('jenkins.user') or \
137 __salt__['config.get']('jenkins:user') or \
138 __salt__['pillar.get']('jenkins.user')
139
140 jenkins_password = __salt__['config.get']('jenkins.password') or \
141 __salt__['config.get']('jenkins:password') or \
142 __salt__['pillar.get']('jenkins.password')
143
144 return (jenkins_url, jenkins_user, jenkins_password)
145
146
147def encode_password(password):
148 """
149 Hash plaintext password by jenkins bcrypt algorithm
150 :param password: plain-text password
151 :returns: bcrypt hashed password
152 """
153 if isinstance(password, str):
154 return bcrypt.hashpw(password, bcrypt.gensalt(prefix=b"2a"))
Dmitry Burmistrovb4416ef2018-04-13 11:22:02 +0400155
156def load_template(salt_url, env):
157 """
158 Return content of file `salt_url`
159 """
160
161 template_path = __salt__['cp.cache_file'](salt_url, env)
162 with open(template_path, 'r') as template_file:
163 template = template_file.read()
164
165 return template
166
167def api_call(name, template, success_msgs, params, display_name):
168 test = __opts__['test'] # noqa
169 ret = {
170 'name': name,
171 'changes': {},
172 'result': False,
173 'comment': '',
174 }
175 result = False
176 if test:
177 status = success_msgs[0]
178 ret['changes'][name] = status
179 ret['comment'] = '%s "%s" %s' % (display_name, name, status.lower())
180 else:
181 call_result = call_groovy_script(template, params)
182 if call_result["code"] == 200 and call_result["msg"].strip() in success_msgs:
183 status = call_result["msg"]
184 if status == success_msgs[0]:
185 ret['changes'][name] = status
186 ret['comment'] = '%s "%s" %s' % (display_name, name, status.lower())
187 result = True
188 else:
189 status = 'FAILED'
190 logger.error(
191 'Jenkins API call failure: %s', call_result["msg"])
192 ret['comment'] = 'Jenkins API call failure: %s' % (call_result[
193 "msg"])
194 ret['result'] = None if test else result
195 return ret
196