blob: c7446fae727e3d08e939796045ab932235d7f8d2 [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
65 token_obj = get_api_crumb(jenkins_url, jenkins_user, jenkins_password)
66 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,
72 auth=(jenkins_user, jenkins_password),
73 data=req_data)
74 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:
114 return tokenReq.json()
Jakub Josef7ae6b242016-12-14 14:41:44 +0100115 elif tokenReq.status_code in [404, 401]:
116 # 404 means CSRF security is disabled, so api crumb is not necessary,
117 # 401 means unauthorized
Jakub Josefe13e2e72016-12-08 13:41:19 +0100118 return None
Jakub Josef8e7385e2016-12-07 21:20:34 +0100119 else:
Jakub Josefe13e2e72016-12-08 13:41:19 +0100120 raise Exception("Cannot obtain Jenkins API crumb. Status code: %s. Text: %s" %
Jakub Josef3de91af2016-12-08 17:03:33 +0100121 (tokenReq.status_code, tokenReq.text))
Jakub Josef8e7385e2016-12-07 21:20:34 +0100122
123
Jakub Josef8e7385e2016-12-07 21:20:34 +0100124def get_jenkins_auth():
125 """
126 Get jenkins params from salt
127 """
128 jenkins_url = __salt__['config.get']('jenkins.url') or \
129 __salt__['config.get']('jenkins:url') or \
130 __salt__['pillar.get']('jenkins.url')
131
132 jenkins_user = __salt__['config.get']('jenkins.user') or \
133 __salt__['config.get']('jenkins:user') or \
134 __salt__['pillar.get']('jenkins.user')
135
136 jenkins_password = __salt__['config.get']('jenkins.password') or \
137 __salt__['config.get']('jenkins:password') or \
138 __salt__['pillar.get']('jenkins.password')
139
140 return (jenkins_url, jenkins_user, jenkins_password)
141
142
143def encode_password(password):
144 """
145 Hash plaintext password by jenkins bcrypt algorithm
146 :param password: plain-text password
147 :returns: bcrypt hashed password
148 """
149 if isinstance(password, str):
150 return bcrypt.hashpw(password, bcrypt.gensalt(prefix=b"2a"))