blob: 64cede66f7f23ab25bcd37b1ecc354e8d930d356 [file] [log] [blame]
import logging
from salt.exceptions import SaltInvocationError
from string import Template
try:
import bcrypt
HAS_BCRYPT = True
except ImportError:
HAS_BCRYPT = False
try:
import requests
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
logger = logging.getLogger(__name__)
def __virtual__():
'''
Only load if bcrypt and requests libraries exist.
'''
if not HAS_BCRYPT:
return (
False,
'Can not load module jenkins_common: bcrypt library not found')
if not HAS_REQUESTS:
return (
False,
'Can not load module jenkins_common: requests library not found')
return True
def call_groovy_script(script, props, username=None,
password=None, success_status_codes=[200]):
"""
Common method for call Jenkins groovy script API
:param script: groovy script template
:param props: groovy script properties
:param username: jenkins username (optional,
if missing creds from sall will be used)
:param password: jenkins password (optional,
if missing creds from sall will be used)
:param success_status_codes: success response status code
(optional) in some cases we want to declare error call as success
:returns: HTTP dict {status,code,msg}
"""
ret = {
"status": "FAILED",
"code": 999,
"msg": ""
}
jenkins_url, jenkins_user, jenkins_password = get_jenkins_auth()
if username:
jenkins_user = username
if password:
jenkins_password = password
if not jenkins_url:
raise SaltInvocationError('No Jenkins URL found.')
token_obj, req_cookies = get_api_crumb(jenkins_url, jenkins_user, jenkins_password)
req_data = {"script": render_groovy_script(script, props)}
if token_obj:
req_data[token_obj["crumbRequestField"]] = token_obj["crumb"]
logger.debug("Calling Jenkins script API with URL: %s", jenkins_url)
req = requests.post('%s/scriptText' % jenkins_url,
auth=(jenkins_user, jenkins_password) if jenkins_user else None,
data=req_data, cookies=req_cookies)
ret["code"] = req.status_code
ret["msg"] = req.text
if req.status_code in success_status_codes:
ret["status"] = "SUCCESS"
logger.debug("Jenkins script API call success: %s", ret)
else:
logger.error("Jenkins script API call failed. \
Return code %s. Text: %s", req.status_code, req.text)
return ret
def render_groovy_script(script_template, props):
"""
Helper method for rendering groovy script with props
:param script_template: groovy script template
:param props: groovy script properties
:returns: generated groovy script
"""
template = Template(script_template)
return template.safe_substitute(props)
def get_api_crumb(jenkins_url=None, jenkins_user=None, jenkins_password=None):
"""
Obtains Jenkins API crumb, if CSRF protection is enabled.
Jenkins params can be given by params or not, if not,
params will be get from salt.
:param jenkins_url: Jenkins URL (optional)
:param jenkins_user: Jenkins admin username (optional)
:param jenkins_password: Jenkins admin password (optional)
:returns: salt-specified state dict
"""
if not jenkins_url:
jenkins_url, jenkins_user, jenkins_password = get_jenkins_auth()
logger.debug("Obtaining Jenkins API crumb for URL: %s", jenkins_url)
tokenReq = requests.get("%s/crumbIssuer/api/json" % jenkins_url,
auth=(jenkins_user, jenkins_password) if jenkins_user else None)
if tokenReq.status_code == 200:
logger.debug("Got Jenkins API crumb: %s", tokenReq.json())
return tokenReq.json(), tokenReq.cookies
elif tokenReq.status_code in [404, 401, 502, 503]:
# 404 means CSRF security is disabled, so api crumb is not necessary,
# 401 means unauthorized
# 50x means jenkins is unavailabe - fail in call_groovy_script, but
# not here, to handle exception in state
logger.debug("Got error %s: %s", str(tokenReq.status_code), tokenReq.reason)
return None, None
else:
raise Exception("Cannot obtain Jenkins API crumb. Status code: %s. Text: %s" %
(tokenReq.status_code, tokenReq.text))
def get_jenkins_auth():
"""
Get jenkins params from salt
"""
jenkins_url = __salt__['config.get']('jenkins.url') or \
__salt__['config.get']('jenkins:url') or \
__salt__['pillar.get']('jenkins.url')
jenkins_user = __salt__['config.get']('jenkins.user') or \
__salt__['config.get']('jenkins:user') or \
__salt__['pillar.get']('jenkins.user')
jenkins_password = __salt__['config.get']('jenkins.password') or \
__salt__['config.get']('jenkins:password') or \
__salt__['pillar.get']('jenkins.password')
return (jenkins_url, jenkins_user, jenkins_password)
def encode_password(password):
"""
Hash plaintext password by jenkins bcrypt algorithm
:param password: plain-text password
:returns: bcrypt hashed password
"""
if isinstance(password, str):
return bcrypt.hashpw(password, bcrypt.gensalt(prefix=b"2a"))
def load_template(salt_url, env):
"""
Return content of file `salt_url`
"""
template_path = __salt__['cp.cache_file'](salt_url, env)
with open(template_path, 'r') as template_file:
template = template_file.read()
return template
def api_call(name, template, success_msgs, params, display_name):
test = __opts__['test'] # noqa
ret = {
'name': name,
'changes': {},
'result': False,
'comment': '',
}
result = False
if test:
status = success_msgs[0]
ret['changes'][name] = status
ret['comment'] = '%s "%s" %s' % (display_name, name, status.lower())
else:
call_result = call_groovy_script(template, params)
if call_result["code"] == 200 and call_result["msg"].strip() in success_msgs:
status = call_result["msg"]
if status == success_msgs[0]:
ret['changes'][name] = status
ret['comment'] = '%s "%s" %s' % (display_name, name, status.lower())
result = True
else:
status = 'FAILED'
logger.error(
'Jenkins API call failure: %s', call_result["msg"])
ret['comment'] = 'Jenkins API call failure: %s' % (call_result[
"msg"])
ret['result'] = None if test else result
return ret