def _add_project(self, project_configuration, skip_project_validation, perfrom_update_call): """Adds a project to Teamscale. The parameter `skip_project_validation` specifies, whether to skip the validation of the project. The parameter `perfrom_update_call` specifies, whether an update call should be made: - If `perfrom_update_call` is set to `True`, re-adding a project with an existing id will update the original project. - If `perfrom_update_call` is set to `False`, re-adding a project with an existing id will result in an error. - Further, if `perfrom_update_call` is set to `True`, but no project with the specified id exists, an error is thrown as well. Args: project_configuration (data.ProjectConfiguration): The project that is to be created (or updated). skip_project_validation (bool): Whether to skip validation of the project. perfrom_update_call (bool): Whether to perform an update call. Returns: requests.Response: object generated by the upload request. Raises: ServiceError: If anything goes wrong. """ service_url = self.get_global_service_url("create-project") parameters = { "skip-project-validation": skip_project_validation, "only-config-update": perfrom_update_call } response = self.put(service_url, parameters=parameters, data=to_json(project_configuration)) response_message = TeamscaleClient._get_response_message(response) if response_message != 'success': raise ServiceError( "ERROR: GET {url}: {status_code}:{message}".format(url=service_url, status_code=response.status_code, message=response_message)) return response
def upload_coverage_data(self, coverage_files, coverage_format, timestamp, message, partition): """Upload coverage reports to Teamscale. It is expected that the given coverage report files can be read from the filesystem. Args: coverage_files (list): list of coverage filenames (strings!) that should be uploaded. Files must be readable. coverage_format (constants.CoverageFormats): the format to use timestamp (datetime.datetime): timestamp (unix format) for which to upload the data message (str): The message to use for the generated upload commit partition (str): The partition's id into which the data should be added (See also: :ref:`FAQ - Partitions<faq-partition>`). Returns: requests.Response: object generated by the request Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("external-report") parameters = { "t": self._get_timestamp_ms(timestamp), "message": message, "partition": partition, "format": coverage_format, "adjusttimestamp": "true" } multiple_files = [('report', open(filename, 'rb')) for filename in coverage_files] response = requests.post(service_url, params=parameters, auth=self.auth_header, verify=self.sslverify, files=multiple_files) if response.status_code != 200: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return response
def get_tasks(self, status="OPEN", details=True, start=0, max=300): """Retrieves the tasks for the client's project from the server. Args: status (constants.TaskStatus): The status to retrieve tickets for details (bool): Whether to retrieve details together with the tasks start (number): From which task number to start listing tasks max (number): Maximum number of tasks to return Returns: List[:class:`data.Task`]): The list of tasks. Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("tasks") parameters = { "status": status, "details": details, "start": start, "max": max, "with-count": False } response = self.get(service_url, parameters=parameters) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return TeamscaleClient._tasks_from_json(response.json())
def get_findings(self, uniform_path, timestamp, recursive=True): """Retrieves the list of findings in the currently active project for the given uniform path at the provided timestamp on the given branch. Args: uniform_path (str): The uniform path to get findings for. timestamp (datetime.datetime): timestamp (unix format) for which to upload the data recursive (bool): Whether to query findings recursively, i.e. also get findings for files under the given path. Returns: List[:class:`data.Finding`]): The list of findings. Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("findings") + uniform_path parameters = { "t": self._get_timestamp_parameter(timestamp=timestamp), "recursive": recursive, "all": True } response = self.get(service_url, parameters=parameters) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return self._findings_from_json(response.json())
def upload_architectures(self, architectures, timestamp, message): """Upload architectures to Teamscale. It is expected that the given architectures can be be read from the filesystem. Args: architectures (dict): mappping of teamscale paths to architecture files that should be uploaded. Files must be readable. timestamp (datetime.datetime): timestamp for which to upload the data message (str): The message to use for the generated upload commit Returns: requests.Response: object generated by the request Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("architecture-upload") parameters = { "t": self._get_timestamp_parameter(timestamp), "adjusttimestamp": "true", "message": message } architecture_files = [(path, open(filename, 'rb')) for path, filename in architectures.items()] response = requests.post(service_url, params=parameters, auth=self.auth_header, verify=self.sslverify, files=architecture_files, timeout=self.timeout) if not response.ok: raise ServiceError("ERROR: POST {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return response
def upload_report(self, report_files, report_format, timestamp, message, partition, move_to_last_commit=True): """Upload reports from external tools to Teamscale. It is expected that the given report files can be read from the filesystem. Args: report_files (list): list of filenames (strings!) that should be uploaded. Files must be readable. report_format (constants.ReportFormats): the format to use timestamp (datetime.datetime): timestamp (unix format) for which to upload the data message (str): The message to use for the generated upload commit partition (str): The partition's id into which the data should be added (See also: :ref:`FAQ - Partitions<faq-partition>`). move_to_last_commit (bool): True to automatically adjust this commit to be the latest otherwise False. Default is True Returns: requests.Response: object generated by the request Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("external-report") parameters = { "t": self._get_timestamp_parameter(timestamp), "message": message, "partition": partition, "format": report_format, "adjusttimestamp": "true", "movetolastcommit": str(move_to_last_commit).lower() } multiple_files = [('report', open(filename, 'rb')) for filename in report_files] response = requests.post(service_url, params=parameters, auth=self.auth_header, verify=self.sslverify, files=multiple_files, timeout=self.timeout) if not response.ok: raise ServiceError("ERROR: POST {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return response
def upload_files_for_precommit_analysis(self, timestamp, precommit_data): """Uploads the provided files for precommit analysis. Args: timestamp (datetime.datetime): The timestamp of the parent commit. precommit_data (data.PreCommitUploadData): The precommit data to upload. """ service_url = self.get_project_service_url("pre-commit") + self._get_timestamp_parameter(timestamp) response = self.put(service_url, data=to_json(precommit_data)) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response))
def check_api_version(self): """Verifies the server's api version and connectivity. Raises: ServiceError: If the version does not match or the server cannot be found. """ url = self.get_global_service_url('service-api-info') response = self.get(url) json_response = response.json() api_version = json_response['apiVersion'] if api_version < 6: raise ServiceError("Server api version " + str( api_version) + " too low and not compatible. This client requires Teamscale 4.1 or newer.")
def get_architectures(self): """Returns the paths of all architecture in the project. Returns: List[str] The architecture names. """ service_url = self.get_project_service_url("arch-assessment") parameters = { "list": True, } response = self.get(service_url, parameters=parameters) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return [architecture_overview['uniformPath'] for architecture_overview in response.json()]
def delete(self, url, parameters=None): """Sends a DELETE request to the given service url. Args: url (str): The URL for which to execute a DELETE request parameters (dict): parameters to attach to the url Returns: requests.Response: request's response Raises: ServiceError: If anything goes wrong """ response = requests.delete(url, params=parameters, auth=self.auth_header, verify=self.sslverify) if response.status_code != 200: raise ServiceError("ERROR: PUT {url}: {r.status_code}:{r.text}".format(url=url, r=response)) return response
def get_baselines(self): """Retrieves a list of baselines from the server for the currently active project. Returns: List[:class:`data.Basenline`]): The list of baselines. Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("baselines") parameters = { "detail": True } headers = {'Accept' : 'application/json'} response = requests.get(service_url, params=parameters, auth=self.auth_header, verify=self.sslverify, headers=headers) if response.status_code != 200: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return [ Baseline(x['name'], x['description'], timestamp=x['timestamp']) for x in response.json() ]
def add_task_comment(self, task_id, comment): """Adds a comment to a task. Args: task_id (number): the task id to which to add the comment comment (str): the comment to add Returns: requests.Response: object generated by the request Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("comment-task") + str(task_id) response = self.put(service_url, data=to_json(comment)) if not response.ok: raise ServiceError("ERROR: PUT {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return response
def put(self, url, json=None, parameters=None, data=None): """Sends a PUT request to the given service url with the json payload as content. Args: url (str): The URL for which to execute a PUT request json: The Object to attach as content, will be serialized to json (only for object that can be serialized by default) parameters (dict): parameters to attach to the url data: The data object to be attached to the request Returns: requests.Response: request's response Raises: ServiceError: If anything goes wrong """ response = requests.put(url, params=parameters, json=json, data=data, headers={'Content-Type': 'application/json'}, auth=self.auth_header, verify=self.sslverify) if response.status_code != 200: raise ServiceError("ERROR: PUT {url}: {r.status_code}:{r.text}".format(url=url, r=response)) return response
def get(self, url, parameters=None): """Sends a GET request to the given service url. Args: url (str): The URL for which to execute a PUT request parameters (dict): parameters to attach to the url Returns: requests.Response: request's response Raises: ServiceError: If anything goes wrong """ headers = {'Accept': 'application/json'} response = requests.get(url, params=parameters, auth=self.auth_header, verify=self.sslverify, headers=headers, timeout=self.timeout) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=url, r=response)) return response
def _parse_findings_response(self, service_url, response): """Parses findings retrieved from Teamscale. Args: service_url (str): The service url. Used for logging. response (requests.Response): The response to parse for findings. Returns: A tuple consisting of three lists: added findings, findings in changed code, and removed findings. Raises: ServiceError: If anything goes wrong. """ if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) added_findings = self._findings_from_json(response.json()['addedFindings']) findings_in_changed_code = self._findings_from_json(response.json()['findingsInChangedCode']) removed_findings = self._findings_from_json(response.json()['removedFindings']) return added_findings, removed_findings, findings_in_changed_code
def get_finding_by_id(self, finding_id, branch=None, timestamp=None): """Retrieves the finding with the given id. Args: finding_id (str): The id of the finding to retrieve. branch (str): The branch from which the finding should be retrieved. timestamp (datetime.datetime): The timestamp (unix format) for which to receive the finding. Returns: data.Finding: The retrieved finding. Raises: ServiceError: If anything goes wrong """ service_url = self.get_project_service_url("findings-by-id") + finding_id parameters = { "t": self._get_timestamp_parameter(timestamp=timestamp, branch=branch), } response = self.get(service_url, parameters=parameters) if not response.ok: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return self._finding_from_json(response.json())