def post_request(self, **values_to_unpack) -> requests: """Performs a HTTP POST request to the Bamboo server. :param values_to_unpack: Values to un-pack in order to construct the HTTP POST request :return: A requests response object :raise: Custom exception on HTTP communication errors """ url = values_to_unpack.get('url', "") headers = values_to_unpack.get('header', "") or self.http_header data = values_to_unpack.get('data', {}) timeout = values_to_unpack.get('timeout', 30) allow_redirects = values_to_unpack.get('allow_redirects', False) try: response = HTTP.post(url=url, auth=self.auth, headers=headers, data=data, timeout=timeout, allow_redirects=allow_redirects) except (requests.ConnectionError, requests.ConnectTimeout, requests.HTTPError, requests.RequestException, requests.Timeout) as exception: error_message = f"Error when requesting URL: '{url}'{LINE_SEP}{exception}" LOGGER.error(error_message) exception = HTTPErrorException(error_message=error_message) raise exception except Exception as exception: error_message = f"Unknown error when requesting URL: '{url}'{LINE_SEP}{exception}" LOGGER.error(error_message) exception = HTTPErrorException(error_message=error_message) raise exception return response
def __load_credentials() -> tuple: if not all([BAMBOO_USER and BAMBOO_PASS]): LOGGER.warning( "No credentials found while initializing the module! Please ensure you set them in explicitly!" ) return None, None return BAMBOO_USER, BAMBOO_PASS
def stop_build(self, server_url: str = None, plan_build_key: str = None) -> dict: """Stop a running plan build from Bamboo using Bamboo API. This method is not officially supported, as so please be aware there might be conflicts when upgrading to a new Bamboo version. What I have observed between Bamboo official release, is that the return code for the POST URL returns different HTTP codes (like 200, 302). It all depends on the implementation in the Bamboo server. :param server_url: Bamboo server URL used in API call [str] Optional. Use this if you have a cluster of Bamboo servers and need to swap between servers. :param plan_build_key: Bamboo plan build key [str] :return: A dictionary containing HTTP status_code and request content """ server_url = server_url or self.server_url url = self.stop_plan_url_mask.format(server_url=server_url) url = f"{url}?planResultKey={plan_build_key}" if self.verbose: LOGGER.debug(f"URL used to stop plan: '{url}'") # Stop a build by performing a HTTP POST request and check HTTP response code http_post_response = self.post_request(url=url) if http_post_response.status_code not in [200, 302]: return self.pack_response_to_client( response=False, status_code=http_post_response.status_code, content=http_post_response.text, url=url) # Send response to client return self.pack_response_to_client( response=True, status_code=http_post_response.status_code, content=http_post_response, url=url)
def query_plan(self, server_url: str = None, plan_key: str = None) -> dict: """Query a plan build using Bamboo API. :param server_url: Bamboo server URL used in API call [str] Optional. Use this if you have a cluster of Bamboo servers and need to swap between servers. :param plan_key: Bamboo plan key [str] :return: A dictionary containing HTTP status_code and request content :raise: Custom exception on JSON encoding error """ server_url = server_url or self.server_url plan_key = plan_key or self.plan_key url = self.plan_results_url_mask.format(server_url=server_url) url = f"{url}{plan_key}.json?max-results=10000" if self.verbose: LOGGER.debug(f"URL used in query: '{url}'") # Query a build by performing a HTTP GET request and check HTTP response code http_get_response = self.get_request(url=url) if http_get_response.status_code != 200: return self.pack_response_to_client( response=False, status_code=http_get_response.status_code, content=http_get_response.text, url=url) try: # Get the JSON reply from the web page http_get_response.encoding = "utf-8" response_json = http_get_response.json() except ValueError as exception: error_message = f"Error encoding to JSON: {exception}" LOGGER.error(error_message) exception = EncodingJSONException(error_message=error_message) raise exception except Exception as exception: error_message = f"Unknown error when trying to return json-encoded content: {exception}" LOGGER.error(error_message) exception = EncodingJSONException(error_message=error_message) raise exception # Send response to client return self.pack_response_to_client( response=True, status_code=http_get_response.status_code, content=response_json, url=url)
def get_artifact(self, url: str = None, destination_file: str = None) -> dict: """Download artifacts from Bamboo plan build run. :param url: URL used in to download the artifact [str] :param destination_file: Full path to destination file [str] :return: A dictionary containing HTTP status_code and request content :raise: Custom exception on download error """ if not url or not destination_file: return {'content': "Incorrect input provided!"} if self.verbose: LOGGER.debug(f"URL used to download artifact: '{url}'") # Query a build by performing a HTTP GET request and check HTTP response code http_get_response = self.get_request(url=url) if http_get_response.status_code != 200: return self.pack_response_to_client( response=False, status_code=http_get_response.status_code, content=http_get_response.text, url=url) try: get_file = requests.get(url) with open(destination_file, 'wb') as f: f.write(get_file.content) except ValueError as exception: error_message = f"Error when downloading artifact: {exception}" LOGGER.error(error_message) exception = DownloadErrorException(error_message=error_message) raise exception except Exception as exception: error_message = f"Unknown error when downloading artifact: {exception}" LOGGER.error(error_message) exception = DownloadErrorException(error_message=error_message) raise exception # Send response to client return self.pack_response_to_client( response=True, status_code=http_get_response.status_code, content=None, url=url)
def query_job_for_artifacts(self, server_url: str = None, plan_build_key: str = None, job_name: str = None, artifact_names: tuple = None) -> dict: """Query Bamboo plan run build for stage artifacts. TODO: add support to get artifacts from sub-dirs as well :param server_url: Bamboo server URL used in API call [str] Optional. Use this if you have a cluster of Bamboo servers and need to swap between servers. :param plan_build_key: Bamboo plan build key [str] :param job_name: Bamboo plan job name [str] :param artifact_names: Names of the artifacts as in Bamboo plan stage job [tuple] :return: A dictionary containing HTTP status_code, request content and list of artifacts :raise: Custom exception on download error """ server_url = server_url or self.server_url # Artifacts to return artifacts = dict() http_failed_conn_counter = 0 for artifact_name in artifact_names: url = self.artifact_url_mask.format(server_url=server_url, plan_build_key=plan_build_key, job_name=job_name, artifact_name=artifact_name) if self.verbose: LOGGER.debug(f"URL used to query for artifacts: '{url}'") # Query a build by performing a HTTP GET request and check HTTP response code http_get_response = self.get_request(url=url) if http_get_response.status_code != 200: http_failed_conn_counter += 1 continue try: # page = requests.get(url).text <-- Works if Bamboo plan does not require AUTH soup = BeautifulSoup(http_get_response.text, 'html.parser') # All "<a href></a>" elements a_href_elements = (soup.find_all('a', href=True)) for a_href_element in a_href_elements: file_path = a_href_element['href'] file_name = a_href_element.extract().get_text() # Do not add HREF value in case PAGE NOT FOUND error if file_name != "Site homepage": artifacts[file_name] = f"{server_url}{file_path}" except ValueError as exception: error_message = f"Error when downloading artifact: {exception}" LOGGER.error(error_message) exception = DownloadErrorException(error_message=error_message) raise exception except Exception as exception: error_message = f"Unknown error when downloading artifact: {exception}" LOGGER.error(error_message) exception = DownloadErrorException(error_message=error_message) raise exception http_return_code = 200 if http_failed_conn_counter == len(artifact_names): http_return_code = 444 response_to_client = self.pack_response_to_client( response=True, status_code=http_return_code, content=None, url=None) response_to_client['artifacts'] = artifacts # Send response to client return response_to_client
def trigger_plan_build(self, server_url: str = None, plan_key: str = None, req_values: tuple = None) -> dict: """Trigger a plan build using Bamboo API. TODO: deal with errors from plan restriction, like strict no of plans to build :param server_url: Bamboo server URL used in API call [str] Optional. Use this if you have a cluster of Bamboo servers and need to swap between servers. :param plan_key: Bamboo plan key [str] :param req_values: Values to insert into request (tuple) :return: A dictionary containing HTTP status_code and request content :raise: Custom exception on JSON encoding error """ server_url = server_url or self.server_url plan_key = plan_key or self.plan_key # Execute all stages by default if no options received request_payload = {'stage&executeAllStages': [True]} if req_values: # req_values[0] = True/False request_payload['stage&executeAllStages'] = [req_values[0]] # Example # req_value[1] = {'bamboo.driver': "xyz", 'bamboo.test': "xyz_1"} # API supports a list as values for key, value in req_values[1].items(): # Use custom revision when triggering build if key.lower() == 'custom.revision': request_payload["bamboo.customRevision"] = [value] continue request_payload["bamboo.{key}".format(key=key)] = [value] url = self.trigger_plan_url_mask.format(server_url=server_url) url = f"{url}{plan_key}.json" if self.verbose: LOGGER.debug(f"URL used to trigger build: '{url}'") # Trigger the build by performing a HTTP POST request and check HTTP response code http_post_response = self.post_request( url=url, data=json.dumps(request_payload)) if http_post_response.status_code != 200: return self.pack_response_to_client( response=False, status_code=http_post_response.status_code, content=http_post_response.text, url=url) try: # Get the JSON reply from the web page http_post_response.encoding = "utf-8" response_json = http_post_response.json() except ValueError as exception: error_message = f"Error encoding to JSON: {exception}" LOGGER.error(error_message) exception = EncodingJSONException(error_message=error_message) raise exception except Exception as exception: error_message = f"Unknown error when trying to return json-encoded content: {exception}" LOGGER.error(error_message) exception = EncodingJSONException(error_message=error_message) raise exception # Send response to client return self.pack_response_to_client( response=True, status_code=http_post_response.status_code, content=response_json, url=url)