blob: 77f9d1db746e3a1dae674390a53bdf4540a39495 [file] [log] [blame]
Alexander Evseev81494492017-09-14 20:42:08 +03001# -*- coding: utf-8 -*-
2'''
3Module for configuring Artifactory.
4===================================
5'''
6
7import json
8import logging
9import requests
Oleh Hryhorovac106192018-01-29 12:24:15 +020010import os
Alexander Evseev81494492017-09-14 20:42:08 +030011
12from collections import OrderedDict
13
14from lxml import etree
15from lxml import objectify
16
Oleg Iurchenko22df72a2018-02-01 01:45:07 +020017from salt.exceptions import CommandExecutionError
Alexander Evseev81494492017-09-14 20:42:08 +030018
19log = logging.getLogger(__name__)
20
21
22def _api_call(endpoint, data=None, headers=None, method='GET',
23 **connection_args):
24
Oleh Hryhorovac106192018-01-29 12:24:15 +020025 if endpoint.startswith('/api') == False:
26 endpoint = '/api' + endpoint
27
28 return _rest_call(endpoint=endpoint,
29 data=data,
30 headers=headers,
31 method=method,
32 **connection_args)
33
34def _rest_call(endpoint, data=None, headers=None, method='GET',
35 **connection_args):
36
Alexander Evseev81494492017-09-14 20:42:08 +030037 log.debug('Got connection args: {}'.format(connection_args))
38
39 # Set default values if empty
40 if 'proto' not in connection_args:
41 connection_args['proto'] = 'http'
42 if 'host' not in connection_args:
43 connection_args['host'] = 'localhost'
44 if 'port' not in connection_args:
45 connection_args['port'] = 80
46
47 base_url = connection_args.get(
48 'url',
49 '{proto}://{host}:{port}/artifactory'.format(**connection_args)
50 )
Alexander Evseev81494492017-09-14 20:42:08 +030051
52 username = connection_args.get('user', 'admin')
53 password = connection_args.get('password', 'password')
54 ssl_verify = connection_args.get('ssl_verify', True)
55
56 # Prepare session object
57 api_connection = requests.Session()
58 api_connection.auth = (username, password)
59 api_connection.verify = ssl_verify
60
61 # Override default method if data given
62 if(data and method == 'GET'):
63 method = 'POST'
64
Oleh Hryhorovac106192018-01-29 12:24:15 +020065 endpoint_url = base_url + endpoint
Alexander Evseev81494492017-09-14 20:42:08 +030066 log.debug('Doing {0} request to {1}'.format(method, endpoint_url))
67
Oleh Hryhorovac106192018-01-29 12:24:15 +020068 # REST API call request
Alexander Evseev81494492017-09-14 20:42:08 +030069 resp = api_connection.request(
70 method=method,
71 url=endpoint_url,
72 data=data,
73 headers=headers
74 )
75
Oleh Hryhorovac106192018-01-29 12:24:15 +020076 if resp.ok:
Alexander Evseev81494492017-09-14 20:42:08 +030077 return True, resp.text
78 else:
Oleg Iurchenko22df72a2018-02-01 01:45:07 +020079 try:
80 errors = json.loads(resp.text).get('errors')
81 if errors:
82 for error in errors:
83 log.error('%(status)s:%(message)s' % error)
84 else:
85 log.error('%(status)s:%(message)s' % json.loads(resp.text))
86 return False, json.loads(resp.text)
87 except ValueError:
88 return False, resp.text
89
Alexander Evseev81494492017-09-14 20:42:08 +030090
Alexander Evseev81494492017-09-14 20:42:08 +030091def get_license(**kwargs):
92 endpoint = '/system/license'
93
94 return _api_call(endpoint, **kwargs)
95
96
97def add_license(license_key, **kwargs):
98 endpoint = '/system/license'
99
100 change_data = {
101 'licenseKey': license_key,
102 }
103
104 return _api_call(
105 endpoint=endpoint,
106 data=json.dumps(change_data),
107 headers={'Content-Type': 'application/json'},
108 **kwargs
109 )
110
111def get_config(**kwargs):
112 endpoint = '/system/configuration'
113
114 return _api_call(endpoint, **kwargs)
115
116
117def set_config(config_data, **kwargs):
118 endpoint = '/system/configuration'
119
120 return _api_call(
121 endpoint=endpoint,
122 data=config_data,
123 headers={'Content-Type': 'application/xml'},
124 **kwargs
125 )
126
127
128def get_ldap_config(name, **kwargs):
129
130 result, config_data = get_config(**kwargs)
131 config = objectify.fromstring(config_data.encode('ascii'))
132
133 # Find existing LDAP settings with specified key ...
134 ldap_config = None
135 for ldap_setting_iter in config.security.ldapSettings.getchildren():
136 if ldap_setting_iter.key.text == name:
137 ldap_config = ldap_setting_iter
138 break
139
140 # ... and create new one if not exists
141 if ldap_config is None:
142 ldap_config = objectify.SubElement(
143 config.security.ldapSettings, 'ldapSetting')
144 objectify.SubElement(ldap_config, 'key')._setText(name)
145
146 return result, etree.tostring(ldap_config)
147
148def set_ldap_config(name, uri, base=None, enabled=True, dn_pattern=None,
149 manager_dn=None, manager_pass=None, search_subtree=True,
150 search_filter='(&(objectClass=inetOrgPerson)(uid={0}))',
151 attr_mail='mail', create_users=True, safe_search=True,
152 **kwargs):
153
154 result, config_data = get_config(**kwargs)
155 config = objectify.fromstring(config_data.encode('ascii'))
156
157 # NOTE! Elements must ber sorted in exact order!
158 key_map = OrderedDict([
159 ('enabled', 'enabled'),
160 ('ldapUrl', 'uri'),
161 ('userDnPattern', 'dn_pattern'),
162 ('search', ''),
163 ('autoCreateUser', 'create_users'),
164 ('emailAttribute', 'attr_mail'),
165 ('ldapPoisoningProtection', 'safe_search'),
166 ])
167
168 key_map_search = OrderedDict([
169 ('searchFilter', 'search_filter'),
170 ('searchBase', 'base'),
171 ('searchSubTree', 'search_subtree'),
172 ('managerDn', 'manager_dn'),
173 ('managerPassword', 'manager_pass'),
174 ])
175
176 # Find existing LDAP settings with specified key ...
177 ldap_config = None
178 for ldap_setting_iter in config.security.ldapSettings.getchildren():
179 if ldap_setting_iter.key.text == name:
180 ldap_config = ldap_setting_iter
181 search_config = ldap_config.search
182 break
183
184 # ... and create new one if not exists
185 if ldap_config is None:
186 ldap_config = objectify.SubElement(
187 config.security.ldapSettings, 'ldapSetting')
188 objectify.SubElement(ldap_config, 'key')._setText(name)
189
190 # LDAP options
191 for xml_key, var_name in key_map.iteritems():
192
193 # Search subtree must follow element order
194 if xml_key == 'search' and not hasattr(ldap_config, 'search'):
195 search_config = objectify.SubElement(ldap_config, 'search')
196 break
197
198 if var_name in locals():
199 # Replace None with empty strings
200 var_value = locals()[var_name] or ''
201 if isinstance(var_value, bool):
202 # Boolean values should be lowercased
203 xml_text = str(var_value).lower()
204 else:
205 xml_text = str(var_value)
206
207 if hasattr(ldap_config, xml_key):
208 ldap_config[xml_key]._setText(xml_text)
209 else:
210 objectify.SubElement(ldap_config, xml_key)._setText(
211 xml_text)
212
213 # Search options (same code as above but using search_config)
214 for xml_key, var_name in key_map_search.iteritems():
215 if var_name in locals():
216 # Replace None with empty strings
217 var_value = locals()[var_name] or ''
218 if isinstance(var_value, bool):
219 # Boolean values should be lowercased
220 xml_text = str(var_value).lower()
221 else:
222 xml_text = str(var_value)
223
224 if hasattr(search_config, xml_key):
225 search_config[xml_key]._setText(xml_text)
226 else:
227 objectify.SubElement(search_config, xml_key)._setText(
228 xml_text)
229
230 change_data = etree.tostring(config)
231
232 return set_config(change_data, **kwargs)
233
234
235def list_repos(**kwargs):
236 endpoint = '/repositories'
237
238 return _api_call(endpoint, **kwargs)
239
240
241def get_repo(name, **kwargs):
242 result, repo_list = list_repos(**kwargs)
243 if name in [r['key'] for r in json.loads(repo_list)]:
244 endpoint = '/repositories/' + name
245 return _api_call(endpoint, **kwargs)
246 else:
247 return True, {}
248
249
250def set_repo(name, repo_config, **kwargs):
251 log.debug('Got repo parameters: {}'.format(repo_config))
252
253 result, repo_list = list_repos(**kwargs)
254 if name in [r['key'] for r in json.loads(repo_list)]:
255 method = 'POST'
256 else:
257 method = 'PUT'
258
259 endpoint = '/repositories/' + name
260
261 return _api_call(
262 endpoint=endpoint,
263 method=method,
264 data=json.dumps(repo_config),
265 headers={'Content-Type': 'application/json'},
266 **kwargs
267 )
Oleh Hryhorovac106192018-01-29 12:24:15 +0200268
269def deploy_artifact(source_file, endpoint, **kwargs):
270
271 endpoint = endpoint + "/" + os.path.basename(source_file)
Oleg Iurchenko22df72a2018-02-01 01:45:07 +0200272 # There is an issue with zero lenght files sending for Requests 2.x
273 # https://github.com/requests/requests/issues/4215
274 # Need to verify filesize before sending.
275 if os.path.getsize(source_file) > 0:
276 with open(source_file, 'rb') as input_file:
277 result, status = _rest_call(
278 endpoint=endpoint,
279 method='PUT',
280 data=input_file,
281 **kwargs
282 )
283 else:
284 result, status = _rest_call(
285 endpoint=endpoint,
286 method='PUT',
287 data='',
288 **kwargs
289 )
290 if result == False:
291 raise CommandExecutionError(status)
292 return status
Oleh Hryhorovac106192018-01-29 12:24:15 +0200293
294def delete_artifact(item_to_delete, **kwargs):
295
296 return _rest_call(
297 endpoint=item_to_delete,
298 method='DELETE',
299 **kwargs
300 )
301