Ales Komarek | 197f443 | 2016-09-02 15:26:17 +0200 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | ''' |
| 3 | Module for fetching artifacts from Artifactory |
| 4 | ''' |
| 5 | |
| 6 | # Import python libs |
| 7 | from __future__ import absolute_import |
| 8 | import os |
| 9 | import base64 |
| 10 | import logging |
| 11 | |
| 12 | # Import Salt libs |
| 13 | import salt.utils |
| 14 | import salt.ext.six.moves.http_client # pylint: disable=import-error,redefined-builtin,no-name-in-module |
| 15 | from salt.ext.six.moves import urllib # pylint: disable=no-name-in-module |
| 16 | from salt.ext.six.moves.urllib.error import HTTPError, URLError # pylint: disable=no-name-in-module |
| 17 | |
| 18 | import json |
| 19 | import requests |
| 20 | |
| 21 | log = logging.getLogger(__name__) |
| 22 | |
| 23 | __virtualname__ = 'artifactory_repo' |
| 24 | |
| 25 | |
| 26 | def __virtual__(): |
| 27 | |
| 28 | return True |
| 29 | |
| 30 | |
| 31 | repo_config = { |
| 32 | "key": "local-repo1", |
| 33 | "rclass" : "local", |
| 34 | "packageType": "generic", |
| 35 | "description": "The local repository public description", |
| 36 | } |
| 37 | |
| 38 | # "repoLayoutRef" : "maven-2-default", |
| 39 | |
| 40 | class ArtifactoryClient: |
| 41 | |
| 42 | def __init__(self, config={}): |
| 43 | |
| 44 | self.files = [] |
| 45 | |
| 46 | client_config = { |
| 47 | 'artifactory_url': 'http://your-instance/artifactory/api', |
| 48 | 'search_prop': 'search/prop', |
| 49 | 'search_name': 'search/artifact', |
| 50 | 'search_repos': 'repositories', |
| 51 | 'username': 'your-user', |
| 52 | 'password': 'password', |
| 53 | 'headers': {'Content-type': 'application/json'} |
| 54 | } |
| 55 | |
| 56 | client_config.update(config) |
| 57 | |
| 58 | # Set instance variables for every value in party_config |
| 59 | for k, v in client_config.items(): |
| 60 | setattr(self, '%s' % (k,), v) |
| 61 | |
| 62 | def create_repository(self, name, config, **connection_args): |
| 63 | repositories = [] |
| 64 | |
| 65 | query = "%s/%s/%s" % (self.artifactory_url, self.search_repos, name) |
| 66 | auth = (self.username, self.password) |
| 67 | |
| 68 | r = requests.put(query, auth=auth, json=config) |
| 69 | print(r.content) |
| 70 | |
| 71 | raw_response = self.query_artifactory(query) |
| 72 | if raw_response is None: |
| 73 | return [] |
| 74 | response = json.loads(raw_response.text) |
| 75 | for line in response: |
| 76 | for item in line: |
| 77 | repositories.append(line) |
| 78 | |
| 79 | if repositories: |
| 80 | return repositories |
| 81 | |
| 82 | return [] |
| 83 | |
| 84 | |
| 85 | def get_repositories(self, repo_type=None, **connection_args): |
| 86 | repositories = [] |
| 87 | |
| 88 | if repo_type is None: |
| 89 | query = "%s/%s" % (self.artifactory_url, self.search_repos) |
| 90 | else: |
| 91 | query = "%s/%s?type=%s" % (self.artifactory_url, |
| 92 | self.search_repos, repo_type) |
| 93 | |
| 94 | raw_response = self.query_artifactory(query) |
| 95 | if raw_response is None: |
| 96 | return [] |
| 97 | response = json.loads(raw_response.text) |
| 98 | for line in response: |
| 99 | for item in line: |
| 100 | repositories.append(line) |
| 101 | |
| 102 | if repositories: |
| 103 | return repositories |
| 104 | |
| 105 | return [] |
| 106 | |
| 107 | |
| 108 | def query_artifactory(self, query, query_type='get'): |
| 109 | """ |
| 110 | Send request to Artifactory API endpoint. |
| 111 | @param: query - Required. The URL (including endpoint) to send to the Artifactory API |
| 112 | @param: query_type - Optional. CRUD method. Defaults to 'get'. |
| 113 | """ |
| 114 | |
| 115 | auth = (self.username, self.password) |
| 116 | query_type = query_type.lower() |
| 117 | |
| 118 | if query_type == "get": |
| 119 | response = requests.get(query, auth=auth, headers=self.headers) |
| 120 | elif query_type == "put": |
| 121 | response = requests.put(query, data=query.split('?', 1)[1], auth=auth, headers=self.headers) |
| 122 | if query_type == "post": |
| 123 | pass |
| 124 | |
| 125 | if not response.ok: |
| 126 | return None |
| 127 | |
| 128 | return response |
| 129 | |
| 130 | def query_file_info(self, filename): |
| 131 | """ |
| 132 | Send request to Artifactory API endpoint for file details. |
| 133 | @param: filename - Required. The shortname of the artifact |
| 134 | """ |
| 135 | query = "%s/storage/%s" % (self.artifactory_url, filename) |
| 136 | |
| 137 | raw_response = self.query_artifactory(query) |
| 138 | if raw_response is None: |
| 139 | return raw_response |
| 140 | response = json.loads(raw_response.text) |
| 141 | |
| 142 | return response |
| 143 | |
| 144 | def find_by_properties(self, properties): |
| 145 | """ |
| 146 | Look up an artifact, or artifacts, in Artifactory by using artifact properties. |
| 147 | @param: properties - List of properties to use as search criteria. |
| 148 | """ |
| 149 | query = "%s/%s?%s" % (self.artifactory_url, |
| 150 | self.search_prop, urlencode(properties)) |
| 151 | raw_response = self.query_artifactory(query) |
| 152 | if raw_response is None: |
| 153 | return raw_response |
| 154 | |
| 155 | response = json.loads(raw_response.text) |
| 156 | |
| 157 | for item in response['results']: |
| 158 | for k, v in item.items(): |
| 159 | setattr(self, '%s' % (k,), v) |
| 160 | |
| 161 | if not response['results']: |
| 162 | return None |
| 163 | |
| 164 | artifact_list = [] |
| 165 | for u in response['results']: |
| 166 | artifact_list.append(os.path.basename(u['uri'])) |
| 167 | |
| 168 | self.files = artifact_list |
| 169 | setattr(self, 'count', len(artifact_list)) |
| 170 | |
| 171 | return "OK" |
| 172 | |
| 173 | def find(self, filename): |
| 174 | """ |
| 175 | Look up an artifact, or artifacts, in Artifactory by |
| 176 | its filename. |
| 177 | @param: filename - Filename of the artifact to search. |
| 178 | """ |
| 179 | query = "%s/%s?name=%s" % (self.artifactory_url, |
| 180 | self.search_name, filename) |
| 181 | raw_response = self.query_artifactory(query) |
| 182 | if raw_response is None: |
| 183 | return raw_response |
| 184 | response = json.loads(raw_response.text) |
| 185 | if len(response['results']) < 1: |
| 186 | return None |
| 187 | |
| 188 | setattr(self, 'name', filename) |
| 189 | setattr(self, 'url', json.dumps(response)) |
| 190 | |
| 191 | return "OK" |
| 192 | |
| 193 | def get_properties(self, filename, properties=None): |
| 194 | """ |
| 195 | Get an artifact's properties, as defined in the Properties tab in |
| 196 | Artifactory. |
| 197 | @param: filename - Filename of artifact of which to get properties. |
| 198 | @param: properties - Optional. List of properties to help filter results. |
| 199 | """ |
| 200 | if properties: |
| 201 | query = "%s?properties=%s" % (filename, ",".join(properties)) |
| 202 | else: |
| 203 | query = "%s?properties" % filename |
| 204 | |
| 205 | raw_response = self.query_artifactory(query) |
| 206 | if raw_response is None: |
| 207 | return raw_response |
| 208 | response = json.loads(raw_response.text) |
| 209 | for key, value in response.items(): |
| 210 | setattr(self, '%s' % (key,), value) |
| 211 | |
| 212 | return "OK" |
| 213 | |
| 214 | |
| 215 | def _client(**connection_args): |
| 216 | ''' |
| 217 | Set up artifactory credentials |
| 218 | |
| 219 | ''' |
| 220 | |
| 221 | prefix = "artifactory" |
| 222 | |
| 223 | # look in connection_args first, then default to config file |
| 224 | def get(key, default=None): |
| 225 | return connection_args.get('connection_' + key, |
| 226 | __salt__['config.get'](prefix, {})).get(key, default) |
| 227 | |
| 228 | client_config = { |
| 229 | 'artifactory_url': 'http://%s:%s/artifactory/api' % (get('host', 'localhost'), get('port', '8080')) |
| 230 | } |
| 231 | |
| 232 | user = get('user', False) |
| 233 | password = get('password', False) |
| 234 | if user and password: |
| 235 | client_config['username'] = user |
| 236 | client_config['password'] = password |
| 237 | |
| 238 | artifactory_client = ArtifactoryClient(client_config) |
| 239 | |
| 240 | return artifactory_client |
| 241 | |
| 242 | |
| 243 | def repo_list(repo_type=None, **connection_args): |
| 244 | ''' |
| 245 | Return a list of available repositories |
| 246 | |
| 247 | CLI Example: |
| 248 | |
| 249 | .. code-block:: bash |
| 250 | |
| 251 | salt '*' artifactory_repo.repo_list |
| 252 | salt '*' artifactory_repo.repo_list REMOTE |
| 253 | salt '*' artifactory_repo.repo_list LOCAL |
| 254 | ''' |
| 255 | ret = {} |
| 256 | |
| 257 | artifactory = _client(**connection_args) |
| 258 | repos = artifactory.get_repositories(repo_type) |
| 259 | |
| 260 | for repo in repos: |
| 261 | if 'key' in repo: |
| 262 | ret[repo.get('key')] = repo |
| 263 | return ret |
| 264 | |
| 265 | |
| 266 | def repo_get(name, **connection_args): |
| 267 | ''' |
| 268 | Return a list of available repositories |
| 269 | |
| 270 | CLI Example: |
| 271 | |
| 272 | .. code-block:: bash |
| 273 | |
| 274 | salt '*' artifactory_repo.repo_get reponame |
| 275 | ''' |
| 276 | |
| 277 | ret = {} |
| 278 | |
| 279 | repos = repo_list(None, **connection_args) |
| 280 | if not name in repos: |
| 281 | return {'Error': "Error retrieving repository {0}".format(name)} |
| 282 | ret[name] = repos[name] |
| 283 | return ret |
| 284 | |
| 285 | |
| 286 | def repo_create(name, repo_type="local", package="generic", url=None, **connection_args): |
| 287 | ''' |
| 288 | Create a artifactory repository |
| 289 | |
| 290 | :param name: new repo name |
| 291 | :param repo_type: new repo type |
| 292 | :param package: new repo package type |
| 293 | "gradle" | "ivy" | "sbt" | "nuget" | "gems" | "npm" | "bower" | |
| 294 | "debian" | "pypi" | "docker" | "vagrant" | "gitlfs" | "yum" | |
| 295 | "generic" |
| 296 | |
| 297 | |
| 298 | CLI Examples: |
| 299 | |
| 300 | .. code-block:: bash |
| 301 | |
| 302 | salt '*' artifactory_repo.repo_create projectname remote generic |
| 303 | |
| 304 | ''' |
| 305 | ret = {} |
| 306 | |
| 307 | if url in connection_args and url == None: |
| 308 | url = connection_args['url'] |
| 309 | |
| 310 | repo = repo_get(name, **connection_args) |
| 311 | |
| 312 | if repo and not "Error" in repo: |
| 313 | log.debug("Repository {0} exists".format(name)) |
| 314 | return repo |
| 315 | |
| 316 | repo_config = { |
| 317 | "key": name, |
| 318 | "rclass" : repo_type, |
| 319 | "packageType": package, |
| 320 | "description": "The local repository public description", |
| 321 | } |
| 322 | |
| 323 | if repo_type == "remote": |
| 324 | repo_config['url'] = url |
| 325 | |
| 326 | artifactory = _client(**connection_args) |
| 327 | artifactory.create_repository(name, repo_config) |
| 328 | return repo_get(name, **connection_args) |