def __init__(self, remote_url): from msrest import Serializer, Deserializer from msrest.exceptions import DeserializationError, SerializationError self.project = None self.repo = None self.uri = None if remote_url is not None: logger.debug("Remote url: %s", remote_url) models = {'_RemoteInfo': self._RemoteInfo} remote_url = remote_url.lower() remote_info = None if _git_remote_info_cache[remote_url]: deserializer = Deserializer(models) try: remote_info = deserializer.deserialize_data( _git_remote_info_cache[remote_url], '_RemoteInfo') except DeserializationError as ex: logger.debug(ex, exc_info=True) if remote_info is not None: self.project = remote_info.project self.repo = remote_info.repository self.uri = remote_info.server_url if remote_info is None: vsts_info = self.get_vsts_info(remote_url) if vsts_info is not None: self.project = vsts_info.repository.project.id self.repo = vsts_info.repository.id apis_path_segment = '/_apis/' apis_path_segment_pos = vsts_info.repository.url.find( apis_path_segment) if apis_path_segment_pos >= 0: self.uri = vsts_info.repository.url[: apis_path_segment_pos] else: self.uri = vsts_info.server_url serializer = Serializer(models) try: _git_remote_info_cache[remote_url] = \ serializer.serialize_data(self._RemoteInfo(self.project, self.repo, self.uri), '_RemoteInfo') except SerializationError as ex: logger.debug(ex, exc_info=True)
class VssClient(object): """VssClient. :param str base_url: Service URL :param Authentication creds: Authenticated credentials. """ def __init__(self, base_url=None, creds=None): self.config = VssClientConfiguration(base_url) self._client = ServiceClient(creds, self.config) _base_client_models = { k: v for k, v in models.__dict__.items() if isinstance(v, type) } self._base_deserialize = Deserializer(_base_client_models) self._base_serialize = Serializer(_base_client_models) self._all_host_types_locations = None self._locations = None self._suppress_fedauth_redirect = True self._force_msa_pass_through = True self.normalized_url = VssClient._normalize_url(base_url) def add_user_agent(self, user_agent): if user_agent is not None: self.config.add_user_agent(user_agent) def _send_request(self, request, headers=None, content=None, **operation_config): """Prepare and send request object according to configuration. :param ClientRequest request: The request object to be sent. :param dict headers: Any headers to add to the request. :param content: Any body data to add to the request. :param config: Any specific config overrides """ if TRACE_ENV_VAR in os.environ and os.environ[TRACE_ENV_VAR] == 'true': print(request.method + ' ' + request.url) logger.debug('%s %s', request.method, request.url) logger.debug('Request content: %s', content) response = self._client.send(request=request, headers=headers, content=content, **operation_config) logger.debug('Response content: %s', response.content) if response.status_code < 200 or response.status_code >= 300: self._handle_error(request, response) return response def _send(self, http_method, location_id, version, route_values=None, query_parameters=None, content=None, media_type='application/json', returns_collection=False): request = self._create_request_message( http_method=http_method, location_id=location_id, route_values=route_values, query_parameters=query_parameters) negotiated_version = self._negotiate_request_version( self._get_resource_location(location_id), version) if version != negotiated_version: logger.info( "Negotiated api version from '%s' down to '%s'. This means the client is newer than the server.", version, negotiated_version) else: logger.debug("Api version '%s'", negotiated_version) # Construct headers headers = { 'Content-Type': media_type + '; charset=utf-8', 'Accept': 'application/json;api-version=' + negotiated_version } if self.config.additional_headers is not None: for key in self.config.additional_headers: headers[key] = self.config.additional_headers[key] if self._suppress_fedauth_redirect: headers['X-TFS-FedAuthRedirect'] = 'Suppress' if self._force_msa_pass_through: headers['X-VSS-ForceMsaPassThrough'] = 'true' if VssClient._session_header_key in VssClient._session_data and VssClient._session_header_key not in headers: headers[VssClient._session_header_key] = VssClient._session_data[ VssClient._session_header_key] response = self._send_request(request=request, headers=headers, content=content) if VssClient._session_header_key in response.headers: VssClient._session_data[ VssClient._session_header_key] = response.headers[ VssClient._session_header_key] if returns_collection: if response.headers.get("transfer-encoding") == 'chunked': wrapper = self._base_deserialize.deserialize_data( response.json(), 'VssJsonCollectionWrapper') else: wrapper = self._base_deserialize('VssJsonCollectionWrapper', response) collection = wrapper.value return collection else: return response def _create_request_message(self, http_method, location_id, route_values=None, query_parameters=None): location = self._get_resource_location(location_id) if location is None: raise ValueError('API resource location ' + location_id + ' is not registered on ' + self.config.base_url + '.') if route_values is None: route_values = {} route_values['area'] = location.area route_values['resource'] = location.resource_name route_template = self._remove_optional_route_parameters( location.route_template, route_values) logger.debug('Route template: %s', location.route_template) url = self._client.format_url(route_template, **route_values) request = ClientRequest(method=http_method, url=self._client.format_url(url)) if query_parameters: request.format_parameters(query_parameters) return request @staticmethod def _remove_optional_route_parameters(route_template, route_values): new_template = '' route_template = route_template.replace('{*', '{') for path_segment in route_template.split('/'): if (len(path_segment) <= 2 or not path_segment[0] == '{' or not path_segment[len(path_segment) - 1] == '}' or path_segment[1:len(path_segment) - 1] in route_values): new_template = new_template + '/' + path_segment return new_template def _get_resource_location(self, location_id): if self.config.base_url not in VssClient._locations_cache: VssClient._locations_cache[ self.config.base_url] = self._get_resource_locations( all_host_types=False) for location in VssClient._locations_cache[self.config.base_url]: if location.id == location_id: return location def _get_resource_locations(self, all_host_types): # Check local client's cached Options first if all_host_types: if self._all_host_types_locations is not None: return self._all_host_types_locations elif self._locations is not None: return self._locations # Next check for options cached on disk if not all_host_types and OPTIONS_FILE_CACHE[self.normalized_url]: try: logger.debug('File cache hit for options on: %s', self.normalized_url) self._locations = self._base_deserialize.deserialize_data( OPTIONS_FILE_CACHE[self.normalized_url], '[ApiResourceLocation]') return self._locations except DeserializationError as ex: logger.debug(ex, exc_info=True) else: logger.debug('File cache miss for options on: %s', self.normalized_url) # Last resort, make the call to the server options_uri = self._combine_url(self.config.base_url, '_apis') request = ClientRequest(method='OPTIONS', url=self._client.format_url(options_uri)) if all_host_types: query_parameters = {'allHostTypes': True} request.format_parameters(query_parameters) headers = {'Accept': 'application/json'} if self._suppress_fedauth_redirect: headers['X-TFS-FedAuthRedirect'] = 'Suppress' if self._force_msa_pass_through: headers['X-VSS-ForceMsaPassThrough'] = 'true' response = self._send_request(request, headers=headers) wrapper = self._base_deserialize('VssJsonCollectionWrapper', response) if wrapper is None: raise VstsClientRequestError( "Failed to retrieve resource locations from: {}".format( options_uri)) collection = wrapper.value returned_locations = self._base_deserialize('[ApiResourceLocation]', collection) if all_host_types: self._all_host_types_locations = returned_locations else: self._locations = returned_locations try: OPTIONS_FILE_CACHE[self.normalized_url] = wrapper.value except SerializationError as ex: logger.debug(ex, exc_info=True) return returned_locations @staticmethod def _negotiate_request_version(location, version): if location is None or version is None: return version pattern = r'(\d+(\.\d)?)(-preview(.(\d+))?)?' match = re.match(pattern, version) requested_api_version = match.group(1) if requested_api_version is not None: requested_api_version = float(requested_api_version) if location.min_version > requested_api_version: # Client is older than the server. The server no longer supports this # resource (deprecated). return elif location.max_version < requested_api_version: # Client is newer than the server. Negotiate down to the latest version # on the server negotiated_version = str(location.max_version) if float(location.released_version) < location.max_version: negotiated_version += '-preview' return negotiated_version else: # We can send at the requested api version. Make sure the resource version # is not bigger than what the server supports negotiated_version = str(requested_api_version) is_preview = match.group(3) is not None if is_preview: negotiated_version += '-preview' if match.group(5) is not None: if location.resource_version < int(match.group(5)): negotiated_version += '.' + str( location.resource_version) else: negotiated_version += '.' + match.group(5) return negotiated_version @staticmethod def _combine_url(part1, part2): return part1.rstrip('/') + '/' + part2.strip('/') def _handle_error(self, request, response): content_type = response.headers.get('Content-Type') error_message = '' if content_type is None or content_type.find('text/plain') < 0: try: wrapped_exception = self._base_deserialize( 'WrappedException', response) if wrapped_exception is not None and wrapped_exception.message is not None: raise VstsServiceError(wrapped_exception) else: # System exceptions from controllers are not returning wrapped exceptions. # Following code is to handle this unusual exception json case. # TODO: dig into this. collection_wrapper = self._base_deserialize( 'VssJsonCollectionWrapper', response) if collection_wrapper is not None and collection_wrapper.value is not None: wrapped_exception = self._base_deserialize( 'ImproperException', collection_wrapper.value) if wrapped_exception is not None and wrapped_exception.message is not None: raise VstsClientRequestError( wrapped_exception.message) # if we get here we still have not raised an exception, try to deserialize as a System Exception system_exception = self._base_deserialize( 'SystemException', response) if system_exception is not None and system_exception.message is not None: raise VstsClientRequestError(system_exception.message) except DeserializationError: pass elif response.content is not None: error_message = response.content.decode("utf-8") + ' ' if response.status_code == 401: full_message_format = '{error_message}The requested resource requires user authentication: {url}' raise VstsAuthenticationError( full_message_format.format(error_message=error_message, url=request.url)) else: full_message_format = '{error_message}Operation returned an invalid status code of {status_code}.' raise VstsClientRequestError( full_message_format.format(error_message=error_message, status_code=response.status_code)) @staticmethod def _normalize_url(url): return url.rstrip('/').lower() _locations_cache = {} _session_header_key = 'X-TFS-Session' _session_data = {_session_header_key: str(uuid.uuid4())}
class LogAnalyticsAlertClient(object): def __init__(self, credentials, subscription_id, base_url=None): self.config = LogAnalyticsManagementClientConfiguration( credentials, subscription_id, base_url) self._client = ServiceClient(self.config.credentials, self.config) client_models = { k: v for k, v in models.__dict__.items() if isinstance(v, type) } self._serialize = Serializer(client_models) self._serialize.basic_types[unicode] = "unicode" self._serialize.serialize_type["dict"] = self._patched_serialize_dict self._deserialize = Deserializer(client_models) self._deserialize.basic_types[unicode] = "unicode" self._deserialize.deserialize_type[ "dict"] = self._patched_deserialize_dict self._deserialize.deserialize_type[ "list"] = self._patched_deserialize_iter self.alert_services = AlertServicesOperations(self._client, self.config, self._serialize, self._deserialize) def _patched_serialize_dict(self, attr, **kwargs): """Hack for MS Serialization class which can't handle Dictionaries properly """ serialized = {} for key, value in attr.items(): try: if isinstance(value, list): serialized[self._serialize.serialize_unicode( key)] = self._serialize.serialize_iter( value, "str", **kwargs) else: serialized[self._serialize.serialize_unicode( key)] = self._serialize.serialize_data( value, type(value).__name__, **kwargs) except ValueError: serialized[self._serialize.serialize_unicode(key)] = None return serialized def _patched_deserialize_dict(self, attr): if isinstance(attr, list): return { x['key']: self._deserialize.deserialize_data(x['value'], type(x["value"]).__name__) for x in attr } return { k: self._deserialize.deserialize_data(v, type(v).__name__) for k, v in attr.items() } def _patched_deserialize_iter(self, attr): if attr is None: return None if not isinstance(attr, (list, set)): raise DeserializationError( "Cannot deserialize as [{}] an object of type {}".format( type(attr).__name__, type(attr))) return [ self._deserialize.deserialize_data(a, type(a).__name__) for a in attr ]