diff --git a/ado_express/__init__.py b/ado_express/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ado_express/main.py b/ado_express/main.py index bdd776b..b9d6567 100644 --- a/ado_express/main.py +++ b/ado_express/main.py @@ -1,367 +1,54 @@ -import os -import sys - -# Needed to enable segregation of projects -sys.path.append(os.path.abspath(".")) - -def is_running_as_executable(): return getattr(sys, 'frozen', False) - -import concurrent.futures import logging +import os import sys import time -from datetime import datetime -from itertools import repeat - -from pytz import timezone - -from ado_express.packages.authentication import MSAuthentication -from ado_express.packages.common.constants import Constants -from ado_express.packages.common.enums.deployment_status_label import \ - DeploymentStatusLabel -from ado_express.packages.common.enums.explicit_release_types import \ - ExplicitReleaseTypes -from ado_express.packages.common.environment_variables import \ - EnvironmentVariables -from ado_express.packages.common.models import DeploymentDetails -from ado_express.packages.common.models.deployment_status import \ - DeploymentStatus -from ado_express.packages.utils import DeploymentPlan -from ado_express.packages.utils.asset_retrievers.release_environment_finder.release_environment_finder import \ - ReleaseEnvironmentFinder -from ado_express.packages.utils.asset_retrievers.release_finder import \ - ReleaseFinder -from ado_express.packages.utils.asset_retrievers.work_item_manager.work_item_manager import \ - WorkItemManager -from ado_express.packages.utils.excel_manager import ExcelManager -from ado_express.packages.utils.release_manager.update_progress_retriever.update_progress_retriever import \ - UpdateProgressRetriever -from ado_express.packages.utils.release_manager.update_release import \ - UpdateRelease -from ado_express.packages.utils.release_note_helpers import needs_deployment - -if is_running_as_executable(): - # Log to console only when running as an executable - logging.basicConfig(stream=sys.stdout, level=logging.INFO, - format='%(levelname)s:%(asctime)s \t%(pathname)s:line:%(lineno)d \t%(message)s') -else: - # Log to a file when not running as an executable and also print to stdout - file_handler = logging.FileHandler(Constants.LOG_FILE_PATH, encoding='utf-8') - file_handler.setLevel(logging.INFO) - - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(logging.INFO) - - formatter = logging.Formatter('%(levelname)s:%(asctime)s \t%(pathname)s:line:%(lineno)d \t%(message)s') - - file_handler.setFormatter(formatter) - console_handler.setFormatter(formatter) - - logger = logging.getLogger() - logger.setLevel(logging.INFO) - - logger.addHandler(file_handler) - logger.addHandler(console_handler) - -logging.info('Starting application') - -constants = Constants() -deployment_plan_file_headers = constants.DEPLOYMENT_PLAN_HEADERS -deployment_plan_path = constants.SEARCH_RESULTS_DEPLOYMENT_PLAN_FILE_PATH -excel_manager = ExcelManager() - -class Startup: - - def __init__(self, environment_variables): - self.environment_variables = environment_variables - self.load_dependencies() - self.initialize_logging() - - #TODO: Create logger class and move this there - def initialize_logging(self): - if self.search_only: - logging.info('Starting the search...') - logging.info(f"Search Date & Time:{self.datetime_now.strftime(self.time_format)}\nResults:\n") - else: - logging.info('Starting the update...') - - - def initialize_excel_configurations(self): - # Create new deployment excel file - new_df = excel_manager.create_dataframe(deployment_plan_file_headers) - excel_manager.save_or_concat_file(new_df, deployment_plan_path, True) - - - def updated_deployment_details_based_on_explicit_inclusion_and_exclusion(self, deployment_details): - new_deployment_details = [] - explicit_deployment_values = self.environment_variables.EXPLICIT_RELEASE_VALUES - - if explicit_deployment_values is None: return deployment_details - - releases_to_deploy = explicit_deployment_values.get(ExplicitReleaseTypes.INCLUDE) - releases_not_to_deploy = explicit_deployment_values.get(ExplicitReleaseTypes.EXCLUDE) - - if releases_to_deploy: [new_deployment_details.append(deployment_detail) if deployment_detail.release_name in releases_to_deploy else 99999 for deployment_detail in deployment_details] - elif releases_not_to_deploy: [new_deployment_details.append(deployment_detail) if deployment_detail.release_name not in releases_not_to_deploy else 99999 for deployment_detail in deployment_details] - - if new_deployment_details == []: logging.error('Found no releases based on the explicit release values provided.') - - return new_deployment_details - - def load_dependencies(self): - self.ms_authentication = MSAuthentication(self.environment_variables) - self.release_finder = ReleaseFinder(self.ms_authentication, self.environment_variables) - self.search_only = self.environment_variables.SEARCH_ONLY - self.via_env = self.environment_variables.VIA_ENV - self.via_latest = self.environment_variables.VIA_ENV_LATEST_RELEASE - self.queries = self.environment_variables.QUERIES - self.time_format = '%Y-%m-%d %H:%M:%S' - self.datetime_now = datetime.now(timezone('US/Eastern')) - - def get_crucial_release_definitions(self, deployment_details): - crucial_release_definitions = [] - # First checks command line args, if not found, then checks the deployment plan file - if self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS is not None and self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS != []: - crucial_release_definitions = self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS - else: - for deployment_detail in deployment_details: - if deployment_detail.is_crucial: - crucial_release_definitions.append(deployment_detail.release_name) - - return crucial_release_definitions - - def get_deployment_details_from_query(self): - work_item_manager = WorkItemManager(self.ms_authentication) - found_releases = dict() - - for query in self.queries: - build_ids = work_item_manager.get_query_build_ids(query) - search_result_releases = self.release_finder.get_releases_via_builds(build_ids) - - for release_definition in search_result_releases: - if release_definition not in found_releases: found_releases[release_definition] = search_result_releases[release_definition] - elif found_releases[release_definition] < search_result_releases[release_definition]: found_releases[release_definition] = search_result_releases[release_definition] - rollback_dict = dict() - deployment_details = [] - - if not found_releases: return deployment_details # Didn't find any releases - - # Get rollback - with concurrent.futures.ThreadPoolExecutor() as executor: - rollbacks = executor.map(self.release_finder.get_release, {k for k, v in found_releases.items()}, repeat(self.via_env), repeat(True), repeat(self.via_latest)) - - for rollback in rollbacks: - if all(rollback.values()): rollback_dict |= rollback # If rollback for target environment is found - else: found_releases.pop(next(iter(rollback))) # Remove key & value from found_releases - - for release_location, target_release in found_releases.items(): - project = release_location.split('/')[0] - release_name = release_location.split('/')[1] - rollback_release = rollback_dict[release_location] - target_release_number = target_release.split('-')[1] - rollback_release_number = rollback_release.split('-')[1] - - if needs_deployment(target_release_number, rollback_release_number): - deployment_detail = DeploymentDetails(project, release_name, target_release_number, rollback_release_number) - deployment_details.append(deployment_detail) - - logging.info(f'Release found from query: Project:{project}, Release Definition:{release_name}, Target:{target_release_number}, Rollback:{rollback_release_number}') - - return deployment_details - - def get_deployment_detail_from_latest_release(self, deployment_detail: DeploymentDetails): - try: - target_release = self.release_finder.get_release(deployment_detail, find_via_env=self.via_env, rollback=False, via_latest=self.via_latest) - rollback_release = self.release_finder.get_release(deployment_detail, find_via_env=self.via_env, rollback=True, via_latest=self.via_latest) - target_release_number = target_release.name.split('-')[1] - rollback_release_number = rollback_release.name.split('-')[1] - - if needs_deployment(target_release_number, rollback_release_number): - deployment_detail = DeploymentDetails(deployment_detail.release_project_name, deployment_detail.release_name, target_release_number, rollback_release_number, deployment_detail.is_crucial) - - logging.info(f'Latest release found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}, Target:{target_release_number}, Rollback:{rollback_release_number}') - return deployment_detail - else: return logging.info(f'No Deployable releases found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}, Latest release found: Target:{target_release_number}, Rollback:{rollback_release_number}') - - except: - logging.error(f'Latest release not found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}\n - Possible cause: The release does not have either the source or target stage you are looking for') - return None - - def search_and_log_details_only(self, deployment_detail: DeploymentDetails): - return self.release_finder.get_releases(deployment_detail, find_via_env=self.via_env) - - def deploy_to_target_or_rollback(self, deployment_detail: DeploymentDetails, rollback: bool=False): - try: - if deployment_detail is not None: # The ThreadPoolExecutor may return None for some releases - update_manager = UpdateRelease(constants, self.ms_authentication, self.environment_variables, self.release_finder) - - release_to_update = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) - - if rollback: - logging.info(f'Attempting to rollback: {deployment_detail.release_name}') - update_manager.roll_back_release(deployment_detail, release_to_update) - - return True - else: - update_manager = UpdateRelease(constants, self.ms_authentication, self.environment_variables, self.release_finder) - attempt_was_successful, update_error = update_manager.update_release(deployment_detail, release_to_update) - - if not attempt_was_successful: - logging.error(f'There was an error with deployment for: {deployment_detail.release_name}.\n:{update_error}') - - return attempt_was_successful - except Exception as e: - logging.error(f'There was an error with deployment for: {deployment_detail.release_name}. Please check their status and continue manually.\nException:{e}') - return False - - def get_deployment_status(self, deployment_detail: DeploymentDetails, rollback: bool=False): - try: - if deployment_detail is not None: - try: - updating_release = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) - except IndexError: - errorMessage = f"Error: Cannot find the release for {deployment_detail.release_name}" - logging.error(errorMessage) - - deployment_status = DeploymentStatus(errorMessage, 0, DeploymentStatusLabel.failed) - return deployment_status - - try: - release_environment_finder = ReleaseEnvironmentFinder(self.ms_authentication, self.environment_variables) - updating_release_environment = release_environment_finder.get_release_environment(deployment_detail, updating_release.id) - except IndexError: - errorMessage = f"Error: Cannot find the release environment for {deployment_detail.release_name} and release ID {updating_release.id}" - logging.error(errorMessage) - - deployment_status = DeploymentStatus(errorMessage, 0, DeploymentStatusLabel.failed) - return deployment_status - - release_progress = UpdateProgressRetriever(self.ms_authentication, self.environment_variables) - current_deployment_status = release_progress.monitor_release_progress(deployment_detail.release_project_name, updating_release, updating_release_environment.id) - - return current_deployment_status - - except Exception as e: - logging.error(f'There was an error with retrieving live deployment status of {updating_release.release_definition}.\nException:{e}') - - - def release_deployment_completed(self, deployment_detail, rollback=False): - update_manager = UpdateRelease(constants, self.ms_authentication, self.environment_variables, self.release_finder) - release_to_update = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) - deployment_is_complete, successfully_completed = update_manager.is_deployment_complete(deployment_detail, release_to_update) - return deployment_is_complete, successfully_completed - - def release_deployment_is_in_progress(self, deployment_detail, rollback): - update_manager = UpdateRelease(constants, self.ms_authentication, self.environment_variables, self.release_finder) - release_to_update = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) - is_in_progress = update_manager.is_deployment_in_progress(deployment_detail, release_to_update) - return is_in_progress - - def run_release_deployments(self, deployment_details, is_deploying_crucial_releases, rollback=False, had_crucial_releases=False): - releases = [] - - if is_deploying_crucial_releases: logging.info('Deploying the crucial releases first') - - with concurrent.futures.ThreadPoolExecutor() as executor: # Then, deploy the rest of the releases - if not is_deploying_crucial_releases and had_crucial_releases: - logging.info('Deploying the rest of the releases') - elif not is_deploying_crucial_releases and had_crucial_releases: - logging.info('Deploying releases') - - releases = executor.map(self.deploy_to_target_or_rollback, deployment_details, repeat(rollback)) - - return releases - - def get_crucial_deployment_from_deployment_details(self, deployment_details, crucial_release_definitions): - return [x for x in deployment_details if x.release_name in crucial_release_definitions] - - def remove_crucial_deployments_from_deployment_details(self, deployment_details, crucial_release_definitions): - return [x for x in deployment_details if x.release_name not in crucial_release_definitions] - - -def confirm_deployment(): - user_input = input("Are you sure you want to deploy the releases? (Y/N): ").strip().lower() - return user_input in ["yes", "y"] +# Append current directory to sys.path for run-type isolation +sys.path.append(os.path.abspath(".")) -def stop_process(): - logging.info(f'Stopping process :(') - exit() +from ado_express.packages.ado_express import ADOExpress +from ado_express.packages.shared import Constants, EnvironmentVariables +from ado_express.packages.toolbox import (DeploymentPlan, ExcelManager, + run_helpers) if __name__ == '__main__': + constants = Constants() environment_variables = EnvironmentVariables() - deployment_plan = DeploymentPlan(constants, environment_variables) - startup = Startup(environment_variables) - task_start = time.perf_counter() + excel_manager = ExcelManager() + ado_express = ADOExpress(environment_variables) deployment_details = None + deployment_plan = DeploymentPlan(constants, environment_variables) + run_start_time = time.perf_counter() - # If a query is provided then do query run first (it'll either be deployed later or stop after notes creation) - if environment_variables.QUERIES: - deployment_details = startup.get_deployment_details_from_query() - else: - # If not a query run then get deployment details from deployment plan - deployment_plan_details = deployment_plan.get_data_from_deployment_plan_file() - # Use deployment plan to get deployment details - if environment_variables.VIA_ENV_LATEST_RELEASE: - with concurrent.futures.ThreadPoolExecutor() as executor: - deployment_details = executor.map(startup.get_deployment_detail_from_latest_release, deployment_plan_details) - # Run search - if environment_variables.SEARCH_ONLY: - # If doing a release notes search then create and export deployment details to excel file - if not is_running_as_executable(): - if environment_variables.QUERIES or environment_variables.VIA_ENV_LATEST_RELEASE: - if deployment_details: - logging.info(f'Exporting Release notes to: {constants.SEARCH_RESULTS_DEPLOYMENT_PLAN_FILE_PATH}') - startup.initialize_excel_configurations() - - for deployment_detail in deployment_details: - if deployment_detail is not None: # The ThreadPoolExecutor may return None for some releases - - row = excel_manager.convert_deployment_detail_to_excel_row(deployment_plan_file_headers, deployment_detail) - excel_manager.save_or_concat_file(row, deployment_plan_path) - else: logging.info(f'No results found - please check the configuration') - else: - # Else run a log-only search - for deployment_detail in deployment_plan_details: - startup.search_and_log_details_only(deployment_detail) - - # Run deployment - elif confirm_deployment(): - # Set deployment details to deployment plan details if it's not a query/latest release run - deployment_details = deployment_plan_details if deployment_details is None else deployment_details - - deployment_details = startup.updated_deployment_details_based_on_explicit_inclusion_and_exclusion(deployment_details) - - #TODO: Create logger class & include this - if deployment_details is None: - logging.error(f'No deployment details found - please check the configurations') - - stop_process() - - crucial_release_definitions = startup.get_crucial_release_definitions(deployment_details) - crucial_deployment_details = [] + is_via_query_run = environment_variables.QUERIES is not None + is_via_environment_latest_release_run = environment_variables.VIA_ENV_LATEST_RELEASE is not False - if crucial_release_definitions: - # Separate crucial & regular deployments based on release definitions that match CRUCIAL_RELEASE_DEFINITIONS env variable list - crucial_deployment_details = startup.get_crucial_deployment_from_deployment_details(deployment_details, crucial_release_definitions) - deployment_details[:] = startup.remove_crucial_deployments_from_deployment_details(deployment_details, crucial_release_definitions) + deployment_details, user_provided_deployment_plan = run_helpers.get_deployment_details( + deployment_plan, + ado_express, + is_via_query_run, + is_via_environment_latest_release_run + ) - if crucial_deployment_details: # First, deploy crucial releases if there are any - with concurrent.futures.ThreadPoolExecutor() as executor: - logging.info('Deploying the crucial releases first') - - executor.map(startup.deploy_to_target_or_rollback, crucial_deployment_details) - executor.shutdown(wait=True) + if environment_variables.SEARCH_ONLY: - with concurrent.futures.ThreadPoolExecutor() as executor: # Then, deploy the rest of the releases - if crucial_deployment_details: - logging.info('Deploying the rest of the releases') - else: - logging.info('Deploying releases') - - executor.map(startup.deploy_to_target_or_rollback, deployment_details) + run_helpers.initiate_search( + ado_express, + deployment_details, + is_via_query_run, + is_via_environment_latest_release_run, + user_provided_deployment_plan + ) + + elif run_helpers.have_deployable_releases(deployment_details, user_provided_deployment_plan) and run_helpers.user_confirmed_deployment(): + + run_helpers.initiate_deployment( + ado_express, + deployment_details, + user_provided_deployment_plan + ) else: - stop_process() + run_helpers.stop_process() - task_end = time.perf_counter() - logging.info(f'Tasks completed in {task_end-task_start} seconds') \ No newline at end of file + run_end_time = time.perf_counter() + logging.info(f'Tasks completed in {run_end_time-run_start_time} seconds.') \ No newline at end of file diff --git a/ado_express/packages/__init__.py b/ado_express/packages/__init__.py index 2f24115..c885a0a 100644 --- a/ado_express/packages/__init__.py +++ b/ado_express/packages/__init__.py @@ -1,3 +1,4 @@ -from .utils import * -from .common import * -from .authentication import * \ No newline at end of file +from .ado_express import * +from .authentication import * +from .shared import * +from .toolbox import * diff --git a/ado_express/packages/ado_express/__init__.py b/ado_express/packages/ado_express/__init__.py new file mode 100644 index 0000000..3e6228c --- /dev/null +++ b/ado_express/packages/ado_express/__init__.py @@ -0,0 +1 @@ +from .ado_express import * \ No newline at end of file diff --git a/ado_express/packages/ado_express/ado_express.py b/ado_express/packages/ado_express/ado_express.py new file mode 100644 index 0000000..5174d9f --- /dev/null +++ b/ado_express/packages/ado_express/ado_express.py @@ -0,0 +1,212 @@ +import concurrent.futures +import logging +from itertools import repeat + +from ado_express.packages.authentication import MSAuthentication +from ado_express.packages.shared import Constants, EnvironmentVariables +from ado_express.packages.shared.enums import DeploymentStatusLabel, ExplicitReleaseTypes +from ado_express.packages.shared.models import DeploymentDetails, DeploymentStatus +from ado_express.packages.toolbox import (ExcelManager, Logger, + ReleaseEnvironmentFinder, + ReleaseFinder, + UpdateProgressRetriever, + UpdateRelease, WorkItemManager, + run_helpers) + + +class ADOExpress: + + def __init__(self, environment_variables: EnvironmentVariables): + self.constants = Constants() + self.excel_manager = ExcelManager() + + self.environment_variables = environment_variables + self.load_dependencies() + + Logger(run_helpers.is_running_as_executable()).log_the_start_of_application(self.search_only) + + def load_dependencies(self): + self.ms_authentication = MSAuthentication(self.environment_variables) + self.release_finder = ReleaseFinder(self.ms_authentication, self.environment_variables) + self.search_only = self.environment_variables.SEARCH_ONLY + self.via_env = self.environment_variables.VIA_ENV + self.via_latest = self.environment_variables.VIA_ENV_LATEST_RELEASE + self.queries = self.environment_variables.QUERIES + + def prepare_result_excel_file(self): + new_df = self.excel_manager.create_dataframe(self.constants.DEPLOYMENT_PLAN_HEADERS) + self.excel_manager.save_or_concat_file(new_df, self.constants.SEARCH_RESULTS_DEPLOYMENT_PLAN_FILE_PATH, True) + + def updated_deployment_details_based_on_explicit_inclusion_and_exclusion(self, deployment_details): + new_deployment_details = [] + explicit_deployment_values = self.environment_variables.EXPLICIT_RELEASE_VALUES + + if explicit_deployment_values is None: return deployment_details + + releases_to_deploy = explicit_deployment_values.get(ExplicitReleaseTypes.INCLUDE) + releases_not_to_deploy = explicit_deployment_values.get(ExplicitReleaseTypes.EXCLUDE) + + if releases_to_deploy: [new_deployment_details.append(deployment_detail) if deployment_detail.release_name in releases_to_deploy else 99999 for deployment_detail in deployment_details] + elif releases_not_to_deploy: [new_deployment_details.append(deployment_detail) if deployment_detail.release_name not in releases_not_to_deploy else 99999 for deployment_detail in deployment_details] + + if new_deployment_details == []: logging.error('Found no releases based on the explicit release values provided.') + + return new_deployment_details + + def get_crucial_release_definitions(self, deployment_details): + crucial_release_definitions = [] + # First checks command line args, if not found, then checks the deployment plan file + if self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS is not None and self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS != []: + crucial_release_definitions = self.environment_variables.CRUCIAL_RELEASE_DEFINITIONS + else: + for deployment_detail in deployment_details: + if deployment_detail.is_crucial: + crucial_release_definitions.append(deployment_detail.release_name) + + return crucial_release_definitions + + def get_deployment_details_from_query(self): + work_item_manager = WorkItemManager(self.ms_authentication) + found_releases = dict() + + for query in self.queries: + build_ids = work_item_manager.get_query_build_ids(query) + search_result_releases = self.release_finder.get_releases_via_builds(build_ids) + + for release_definition in search_result_releases: + if release_definition not in found_releases: found_releases[release_definition] = search_result_releases[release_definition] + elif found_releases[release_definition] < search_result_releases[release_definition]: found_releases[release_definition] = search_result_releases[release_definition] + + rollback_dict = dict() + deployment_details = [] + + if not found_releases: return deployment_details + + # Get rollback + with concurrent.futures.ThreadPoolExecutor() as executor: + rollbacks = executor.map(self.release_finder.get_release, {k for k, v in found_releases.items()}, repeat(self.via_env), repeat(True), repeat(self.via_latest)) + + for rollback in rollbacks: + if all(rollback.values()): rollback_dict |= rollback # If found rollback, add it to rollback_dict + else: found_releases.pop(next(iter(rollback))) # Else remove release key & value from found_releases + + for release_location, target_release in found_releases.items(): + project = release_location.split('/')[0] + release_name = release_location.split('/')[1] + rollback_release = rollback_dict[release_location] + target_release_number = target_release.split('-')[1] + rollback_release_number = rollback_release.split('-')[1] + + if run_helpers.needs_deployment(target_release_number, rollback_release_number): + deployment_detail = DeploymentDetails(project, release_name, target_release_number, rollback_release_number) + deployment_details.append(deployment_detail) + + logging.info(f'Release found from query: Project:{project}, Release Definition:{release_name}, Target:{target_release_number}, Rollback:{rollback_release_number}') + + return deployment_details + + def get_deployment_detail_from_latest_release(self, deployment_detail: DeploymentDetails): + try: + target_release = self.release_finder.get_release(deployment_detail, find_via_env=self.via_env, rollback=False, via_latest=self.via_latest) + rollback_release = self.release_finder.get_release(deployment_detail, find_via_env=self.via_env, rollback=True, via_latest=self.via_latest) + target_release_number = target_release.name.split('-')[1] + rollback_release_number = rollback_release.name.split('-')[1] + + if run_helpers.needs_deployment(target_release_number, rollback_release_number): + deployment_detail = DeploymentDetails(deployment_detail.release_project_name, deployment_detail.release_name, target_release_number, rollback_release_number, deployment_detail.is_crucial) + + logging.info(f'Latest release found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}, Target:{target_release_number}, Rollback:{rollback_release_number}') + return deployment_detail + else: return logging.info(f'No Deployable releases found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}, Latest release found: Target:{target_release_number}, Rollback:{rollback_release_number}') + + except: + logging.error(f'Latest release not found: Project:{deployment_detail.release_project_name}, Release Definition:{deployment_detail.release_name}\n - Possible cause: The release does not have either the source or target stage you are looking for') + return None + + def search_and_log_details_only(self, deployment_detail: DeploymentDetails): + return self.release_finder.get_releases(deployment_detail, find_via_env=self.via_env) + + def deploy_to_target_or_rollback(self, deployment_detail: DeploymentDetails, rollback: bool=False): + try: + if deployment_detail is not None: # The ThreadPoolExecutor may return None for some releases + update_manager = UpdateRelease(self.constants, self.ms_authentication, self.environment_variables, self.release_finder) + + release_to_update = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) + + if rollback: + logging.info(f'Attempting to rollback: {deployment_detail.release_name}') + update_manager.roll_back_release(deployment_detail, release_to_update) + + return True + else: + update_manager = UpdateRelease(self.constants, self.ms_authentication, self.environment_variables, self.release_finder) + attempt_was_successful, update_error = update_manager.update_release(deployment_detail, release_to_update) + + if not attempt_was_successful: + logging.error(f'There was an error with deployment for: {deployment_detail.release_name}.\n:{update_error}') + + return attempt_was_successful + except Exception as e: + logging.error(f'There was an error with deployment for: {deployment_detail.release_name}. Please check their status and continue manually.\nException:{e}') + return False + + def get_deployment_status(self, deployment_detail: DeploymentDetails, rollback: bool=False): + try: + if deployment_detail is not None: + try: + updating_release = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) + except IndexError: + errorMessage = f"Error: Cannot find the release for {deployment_detail.release_name}" + logging.error(errorMessage) + + deployment_status = DeploymentStatus(errorMessage, 0, DeploymentStatusLabel.failed) + return deployment_status + + try: + release_environment_finder = ReleaseEnvironmentFinder(self.ms_authentication, self.environment_variables) + updating_release_environment = release_environment_finder.get_release_environment(deployment_detail, updating_release.id) + except IndexError: + errorMessage = f"Error: Cannot find the release environment for {deployment_detail.release_name} and release ID {updating_release.id}" + logging.error(errorMessage) + + deployment_status = DeploymentStatus(errorMessage, 0, DeploymentStatusLabel.failed) + return deployment_status + + release_progress = UpdateProgressRetriever(self.ms_authentication, self.environment_variables) + current_deployment_status = release_progress.monitor_release_progress(deployment_detail.release_project_name, updating_release, updating_release_environment.id) + + return current_deployment_status + + except Exception as e: + logging.error(f'There was an error with retrieving live deployment status of {updating_release.release_definition}.\nException:{e}') + + + def release_deployment_completed(self, deployment_detail, rollback=False): + update_manager = UpdateRelease(self.constants, self.ms_authentication, self.environment_variables, self.release_finder) + + release_to_update = self.release_finder.get_release(deployment_detail, self.via_env, rollback, self.via_latest) + + deployment_is_complete, successfully_completed = update_manager.is_deployment_complete(deployment_detail, release_to_update) + + return deployment_is_complete, successfully_completed + + def run_release_deployments(self, deployment_details, is_deploying_crucial_releases, rollback=False, had_crucial_releases=False): + releases = [] + + if is_deploying_crucial_releases: logging.info('Deploying the crucial releases first') + + with concurrent.futures.ThreadPoolExecutor() as executor: # Then, deploy the rest of the releases + if not is_deploying_crucial_releases and had_crucial_releases: + logging.info('Deploying the rest of the releases') + elif not is_deploying_crucial_releases and had_crucial_releases: + logging.info('Deploying releases') + + releases = executor.map(self.deploy_to_target_or_rollback, deployment_details, repeat(rollback)) + + return releases + + def get_crucial_deployment_from_deployment_details(self, deployment_details, crucial_release_definitions): + return [x for x in deployment_details if x.release_name in crucial_release_definitions] + + def remove_crucial_deployments_from_deployment_details(self, deployment_details, crucial_release_definitions): + return [x for x in deployment_details if x.release_name not in crucial_release_definitions] \ No newline at end of file diff --git a/ado_express/packages/authentication/ms_authentication/ms_authentication.py b/ado_express/packages/authentication/ms_authentication/ms_authentication.py index 0abc2b3..4bb3196 100644 --- a/ado_express/packages/authentication/ms_authentication/ms_authentication.py +++ b/ado_express/packages/authentication/ms_authentication/ms_authentication.py @@ -1,7 +1,7 @@ from azure.devops.connection import Connection from msrest.authentication import BasicAuthentication -from ado_express.packages.common import EnvironmentVariables +from ado_express.packages.shared import EnvironmentVariables class MSAuthentication: diff --git a/ado_express/packages/common/enums/__init__.py b/ado_express/packages/common/enums/__init__.py deleted file mode 100644 index 7e5086a..0000000 --- a/ado_express/packages/common/enums/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .environment_statuses import * -from .relation_types import * \ No newline at end of file diff --git a/ado_express/packages/common/__init__.py b/ado_express/packages/shared/__init__.py similarity index 100% rename from ado_express/packages/common/__init__.py rename to ado_express/packages/shared/__init__.py diff --git a/ado_express/packages/common/constants.py b/ado_express/packages/shared/constants.py similarity index 100% rename from ado_express/packages/common/constants.py rename to ado_express/packages/shared/constants.py diff --git a/ado_express/packages/shared/enums/__init__.py b/ado_express/packages/shared/enums/__init__.py new file mode 100644 index 0000000..b188882 --- /dev/null +++ b/ado_express/packages/shared/enums/__init__.py @@ -0,0 +1,5 @@ +from .deployment_status_label import * +from .environment_statuses import * +from .explicit_release_types import * +from .meta_enum import * +from .relation_types import * diff --git a/ado_express/packages/common/enums/base_enum.py b/ado_express/packages/shared/enums/base_enum.py similarity index 100% rename from ado_express/packages/common/enums/base_enum.py rename to ado_express/packages/shared/enums/base_enum.py diff --git a/ado_express/packages/common/enums/deployment_status_label.py b/ado_express/packages/shared/enums/deployment_status_label.py similarity index 100% rename from ado_express/packages/common/enums/deployment_status_label.py rename to ado_express/packages/shared/enums/deployment_status_label.py diff --git a/ado_express/packages/common/enums/environment_statuses.py b/ado_express/packages/shared/enums/environment_statuses.py similarity index 100% rename from ado_express/packages/common/enums/environment_statuses.py rename to ado_express/packages/shared/enums/environment_statuses.py diff --git a/ado_express/packages/common/enums/explicit_release_types.py b/ado_express/packages/shared/enums/explicit_release_types.py similarity index 100% rename from ado_express/packages/common/enums/explicit_release_types.py rename to ado_express/packages/shared/enums/explicit_release_types.py diff --git a/ado_express/packages/common/enums/meta_enum.py b/ado_express/packages/shared/enums/meta_enum.py similarity index 100% rename from ado_express/packages/common/enums/meta_enum.py rename to ado_express/packages/shared/enums/meta_enum.py diff --git a/ado_express/packages/common/enums/relation_types.py b/ado_express/packages/shared/enums/relation_types.py similarity index 100% rename from ado_express/packages/common/enums/relation_types.py rename to ado_express/packages/shared/enums/relation_types.py diff --git a/ado_express/packages/common/environment_variables.py b/ado_express/packages/shared/environment_variables.py similarity index 98% rename from ado_express/packages/common/environment_variables.py rename to ado_express/packages/shared/environment_variables.py index ffcc3bd..e9c7afa 100644 --- a/ado_express/packages/common/environment_variables.py +++ b/ado_express/packages/shared/environment_variables.py @@ -2,10 +2,11 @@ import os import re import sys + import validators from dotenv import load_dotenv -from ado_express.packages.common.enums.explicit_release_types import ExplicitReleaseTypes +from ado_express.packages.shared.enums import ExplicitReleaseTypes none_types = ["none", "null", "nill", " ", ""] diff --git a/ado_express/packages/common/models/__init__.py b/ado_express/packages/shared/models/__init__.py similarity index 100% rename from ado_express/packages/common/models/__init__.py rename to ado_express/packages/shared/models/__init__.py diff --git a/ado_express/packages/common/models/deployment_details.py b/ado_express/packages/shared/models/deployment_details.py similarity index 100% rename from ado_express/packages/common/models/deployment_details.py rename to ado_express/packages/shared/models/deployment_details.py diff --git a/ado_express/packages/common/models/deployment_status.py b/ado_express/packages/shared/models/deployment_status.py similarity index 100% rename from ado_express/packages/common/models/deployment_status.py rename to ado_express/packages/shared/models/deployment_status.py diff --git a/ado_express/packages/common/models/release_details.py b/ado_express/packages/shared/models/release_details.py similarity index 100% rename from ado_express/packages/common/models/release_details.py rename to ado_express/packages/shared/models/release_details.py diff --git a/ado_express/packages/common/models/release_environment.py b/ado_express/packages/shared/models/release_environment.py similarity index 100% rename from ado_express/packages/common/models/release_environment.py rename to ado_express/packages/shared/models/release_environment.py diff --git a/ado_express/packages/toolbox/__init__.py b/ado_express/packages/toolbox/__init__.py new file mode 100644 index 0000000..9b301c7 --- /dev/null +++ b/ado_express/packages/toolbox/__init__.py @@ -0,0 +1,5 @@ +from .asset_managers import * +from .release_manager import * +from .excel_manager import * +from .logger import * +from .run_helpers import * diff --git a/ado_express/packages/toolbox/asset_managers/__init__.py b/ado_express/packages/toolbox/asset_managers/__init__.py new file mode 100644 index 0000000..cdc5370 --- /dev/null +++ b/ado_express/packages/toolbox/asset_managers/__init__.py @@ -0,0 +1,4 @@ +from .deployment_plan import * +from .release_environment_finder import * +from .release_finder import * +from .work_item_manager import * diff --git a/ado_express/packages/utils/asset_retrievers/deployment_plan/__init__.py b/ado_express/packages/toolbox/asset_managers/deployment_plan/__init__.py similarity index 100% rename from ado_express/packages/utils/asset_retrievers/deployment_plan/__init__.py rename to ado_express/packages/toolbox/asset_managers/deployment_plan/__init__.py diff --git a/ado_express/packages/utils/asset_retrievers/deployment_plan/deployment_plan.py b/ado_express/packages/toolbox/asset_managers/deployment_plan/deployment_plan.py similarity index 85% rename from ado_express/packages/utils/asset_retrievers/deployment_plan/deployment_plan.py rename to ado_express/packages/toolbox/asset_managers/deployment_plan/deployment_plan.py index 060af1c..ded1a67 100644 --- a/ado_express/packages/utils/asset_retrievers/deployment_plan/deployment_plan.py +++ b/ado_express/packages/toolbox/asset_managers/deployment_plan/deployment_plan.py @@ -1,9 +1,9 @@ import numpy as np import pandas as pd -from ado_express.packages.common.constants import Constants -from ado_express.packages.common.environment_variables import EnvironmentVariables -from ado_express.packages.common.models import DeploymentDetails +from ado_express.packages.shared.constants import Constants +from ado_express.packages.shared.environment_variables import EnvironmentVariables +from ado_express.packages.shared.models import DeploymentDetails class DeploymentPlan(): diff --git a/ado_express/packages/toolbox/asset_managers/release_environment_finder/__init__.py b/ado_express/packages/toolbox/asset_managers/release_environment_finder/__init__.py new file mode 100644 index 0000000..1b5816b --- /dev/null +++ b/ado_express/packages/toolbox/asset_managers/release_environment_finder/__init__.py @@ -0,0 +1 @@ +from .release_environment_finder import * diff --git a/ado_express/packages/utils/asset_retrievers/release_environment_finder/release_environment_finder.py b/ado_express/packages/toolbox/asset_managers/release_environment_finder/release_environment_finder.py similarity index 91% rename from ado_express/packages/utils/asset_retrievers/release_environment_finder/release_environment_finder.py rename to ado_express/packages/toolbox/asset_managers/release_environment_finder/release_environment_finder.py index 3cde938..b81a97a 100644 --- a/ado_express/packages/utils/asset_retrievers/release_environment_finder/release_environment_finder.py +++ b/ado_express/packages/toolbox/asset_managers/release_environment_finder/release_environment_finder.py @@ -1,7 +1,6 @@ from ado_express.packages.authentication import MSAuthentication -from ado_express.packages.common.environment_variables import \ - EnvironmentVariables -from ado_express.packages.common.models import ReleaseEnvironment +from ado_express.packages.shared.environment_variables import EnvironmentVariables +from ado_express.packages.shared.models import ReleaseEnvironment class ReleaseEnvironmentFinder: diff --git a/ado_express/packages/utils/asset_retrievers/release_finder/__init__.py b/ado_express/packages/toolbox/asset_managers/release_finder/__init__.py similarity index 100% rename from ado_express/packages/utils/asset_retrievers/release_finder/__init__.py rename to ado_express/packages/toolbox/asset_managers/release_finder/__init__.py diff --git a/ado_express/packages/utils/asset_retrievers/release_finder/release_finder.py b/ado_express/packages/toolbox/asset_managers/release_finder/release_finder.py similarity index 97% rename from ado_express/packages/utils/asset_retrievers/release_finder/release_finder.py rename to ado_express/packages/toolbox/asset_managers/release_finder/release_finder.py index 200107b..c8dee3a 100644 --- a/ado_express/packages/utils/asset_retrievers/release_finder/release_finder.py +++ b/ado_express/packages/toolbox/asset_managers/release_finder/release_finder.py @@ -3,12 +3,11 @@ from itertools import repeat from ado_express.packages.authentication import MSAuthentication -from ado_express.packages.common.constants import Constants -from ado_express.packages.common.enums import ReleaseEnvironmentStatuses -from ado_express.packages.common.environment_variables import \ +from ado_express.packages.shared.enums import ReleaseEnvironmentStatuses +from ado_express.packages.shared.environment_variables import \ EnvironmentVariables -from ado_express.packages.common.models import DeploymentDetails -from ado_express.packages.common.models.release_details import ReleaseDetails +from ado_express.packages.shared.models import DeploymentDetails +from ado_express.packages.shared.models.release_details import ReleaseDetails class ReleaseFinder: diff --git a/ado_express/packages/utils/asset_retrievers/work_item_manager/__init__.py b/ado_express/packages/toolbox/asset_managers/work_item_manager/__init__.py similarity index 100% rename from ado_express/packages/utils/asset_retrievers/work_item_manager/__init__.py rename to ado_express/packages/toolbox/asset_managers/work_item_manager/__init__.py diff --git a/ado_express/packages/utils/asset_retrievers/work_item_manager/work_item_manager.py b/ado_express/packages/toolbox/asset_managers/work_item_manager/work_item_manager.py similarity index 98% rename from ado_express/packages/utils/asset_retrievers/work_item_manager/work_item_manager.py rename to ado_express/packages/toolbox/asset_managers/work_item_manager/work_item_manager.py index e999902..91f6971 100644 --- a/ado_express/packages/utils/asset_retrievers/work_item_manager/work_item_manager.py +++ b/ado_express/packages/toolbox/asset_managers/work_item_manager/work_item_manager.py @@ -2,7 +2,7 @@ from ado_express.packages.authentication.ms_authentication.ms_authentication import \ MSAuthentication -from ado_express.packages.common.enums import RelationTypes +from ado_express.packages.shared.enums import RelationTypes class WorkItemManager: diff --git a/ado_express/packages/utils/excel_manager/__init__.py b/ado_express/packages/toolbox/excel_manager/__init__.py similarity index 100% rename from ado_express/packages/utils/excel_manager/__init__.py rename to ado_express/packages/toolbox/excel_manager/__init__.py diff --git a/ado_express/packages/utils/excel_manager/excel_manager.py b/ado_express/packages/toolbox/excel_manager/excel_manager.py similarity index 96% rename from ado_express/packages/utils/excel_manager/excel_manager.py rename to ado_express/packages/toolbox/excel_manager/excel_manager.py index 062ae1e..a2928a0 100644 --- a/ado_express/packages/utils/excel_manager/excel_manager.py +++ b/ado_express/packages/toolbox/excel_manager/excel_manager.py @@ -3,7 +3,7 @@ from typing import List -from ado_express.packages.common.models import DeploymentDetails +from ado_express.packages.shared.models import DeploymentDetails class ExcelManager: diff --git a/ado_express/packages/toolbox/logger/__init__.py b/ado_express/packages/toolbox/logger/__init__.py new file mode 100644 index 0000000..4c98ddd --- /dev/null +++ b/ado_express/packages/toolbox/logger/__init__.py @@ -0,0 +1 @@ +from .logger import * diff --git a/ado_express/packages/toolbox/logger/logger.py b/ado_express/packages/toolbox/logger/logger.py new file mode 100644 index 0000000..5a12940 --- /dev/null +++ b/ado_express/packages/toolbox/logger/logger.py @@ -0,0 +1,46 @@ +import logging +import sys +from datetime import datetime + +from pytz import timezone + +from ado_express.packages.shared.constants import Constants + + +class Logger: + + def __init__(self, is_running_as_executable): + if is_running_as_executable: self.log_to_console_only() + else: self.log_to_file_and_console() + + def log_to_console_only(): + logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(levelname)s:%(asctime)s \t%(pathname)s:line:%(lineno)d \t%(message)s') + + def log_to_file_and_console(): + file_handler = logging.FileHandler(Constants.LOG_FILE_PATH, encoding='utf-8') + file_handler.setLevel(logging.INFO) + + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(logging.INFO) + + formatter = logging.Formatter('%(levelname)s:%(asctime)s \t%(pathname)s:line:%(lineno)d \t%(message)s') + + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + logging.info('Starting application') + + def log_the_start_of_application(self, is_search_only: bool): + if is_search_only: + time_format = '%Y-%m-%d %H:%M:%S' + datetime_now = datetime.now(timezone('US/Eastern')) + logging.info('Starting the search...') + logging.info(f"Search Date & Time:{datetime_now.strftime(time_format)}\nResults:\n") + else: + logging.info('Starting the update...') \ No newline at end of file diff --git a/ado_express/packages/toolbox/release_manager/__init__.py b/ado_express/packages/toolbox/release_manager/__init__.py new file mode 100644 index 0000000..2c0c181 --- /dev/null +++ b/ado_express/packages/toolbox/release_manager/__init__.py @@ -0,0 +1,2 @@ +from .update_progress_retriever import * +from .update_release import * diff --git a/ado_express/packages/toolbox/release_manager/update_progress_retriever/__init__.py b/ado_express/packages/toolbox/release_manager/update_progress_retriever/__init__.py new file mode 100644 index 0000000..628f49a --- /dev/null +++ b/ado_express/packages/toolbox/release_manager/update_progress_retriever/__init__.py @@ -0,0 +1 @@ +from .update_progress_retriever import * diff --git a/ado_express/packages/utils/release_manager/update_progress_retriever/update_progress_retriever.py b/ado_express/packages/toolbox/release_manager/update_progress_retriever/update_progress_retriever.py similarity index 91% rename from ado_express/packages/utils/release_manager/update_progress_retriever/update_progress_retriever.py rename to ado_express/packages/toolbox/release_manager/update_progress_retriever/update_progress_retriever.py index 6c7fa57..1dd602f 100644 --- a/ado_express/packages/utils/release_manager/update_progress_retriever/update_progress_retriever.py +++ b/ado_express/packages/toolbox/release_manager/update_progress_retriever/update_progress_retriever.py @@ -3,18 +3,9 @@ import requests -from ado_express.packages.authentication.ms_authentication.ms_authentication import \ - MSAuthentication -from ado_express.packages.common.enums.deployment_status_label import \ - DeploymentStatusLabel -from ado_express.packages.common.enums.environment_statuses import \ - ReleaseEnvironmentStatuses -from ado_express.packages.common.environment_variables import \ - EnvironmentVariables -from ado_express.packages.common.models.deployment_status import \ - DeploymentStatus -from ado_express.packages.utils.asset_retrievers.release_environment_finder.release_environment_finder import \ - ReleaseEnvironmentFinder +from ado_express.packages.authentication import MSAuthentication +from ado_express.packages.shared import DeploymentStatus, DeploymentStatusLabel, EnvironmentVariables, ReleaseEnvironmentStatuses +from ado_express.packages.toolbox.asset_managers.release_environment_finder.release_environment_finder import ReleaseEnvironmentFinder class UpdateProgressRetriever: diff --git a/ado_express/packages/utils/release_manager/__init__.py b/ado_express/packages/toolbox/release_manager/update_release/__init__.py similarity index 100% rename from ado_express/packages/utils/release_manager/__init__.py rename to ado_express/packages/toolbox/release_manager/update_release/__init__.py diff --git a/ado_express/packages/utils/release_manager/update_release/update_release.py b/ado_express/packages/toolbox/release_manager/update_release/update_release.py similarity index 93% rename from ado_express/packages/utils/release_manager/update_release/update_release.py rename to ado_express/packages/toolbox/release_manager/update_release/update_release.py index 01fb6b1..400eb14 100644 --- a/ado_express/packages/utils/release_manager/update_release/update_release.py +++ b/ado_express/packages/toolbox/release_manager/update_release/update_release.py @@ -1,16 +1,10 @@ import logging -import time from azure.devops.v5_1.release.models import ReleaseEnvironmentUpdateMetadata from ado_express.packages.authentication import MSAuthentication -from ado_express.packages.common.constants import Constants -from ado_express.packages.common.enums import ReleaseEnvironmentStatuses -from ado_express.packages.common.environment_variables import \ - EnvironmentVariables -from ado_express.packages.utils.asset_retrievers import ReleaseFinder -from ado_express.packages.utils.asset_retrievers.release_environment_finder import \ - ReleaseEnvironmentFinder +from ado_express.packages.shared import Constants, EnvironmentVariables, ReleaseEnvironmentStatuses +from ado_express.packages.toolbox.asset_managers import ReleaseEnvironmentFinder, ReleaseFinder class UpdateRelease: diff --git a/ado_express/packages/toolbox/run_helpers/__init__.py b/ado_express/packages/toolbox/run_helpers/__init__.py new file mode 100644 index 0000000..c8d4744 --- /dev/null +++ b/ado_express/packages/toolbox/run_helpers/__init__.py @@ -0,0 +1 @@ +from .run_helpers import * diff --git a/ado_express/packages/toolbox/run_helpers/run_helpers.py b/ado_express/packages/toolbox/run_helpers/run_helpers.py new file mode 100644 index 0000000..1791c46 --- /dev/null +++ b/ado_express/packages/toolbox/run_helpers/run_helpers.py @@ -0,0 +1,107 @@ +import concurrent.futures +import logging +import sys + +from ado_express.packages.shared import Constants +from ado_express.packages.toolbox import ExcelManager + +constants = Constants() +excel_manager = ExcelManager() + +def user_confirmed_deployment(): + user_input = input("Are you sure you want to deploy the releases? (Y/N): ").strip().lower() + return user_input in ["yes", "y"] + +def stop_process(): + logging.info(f'Stopping process :(') + exit() + +def is_running_as_executable(): return getattr(sys, 'frozen', False) + +def get_deployment_details(deployment_plan, ado_express, is_query_run, is_via_environment_latest_release_run): + deployment_details = user_provided_deployment_plan = None + + if is_query_run: + deployment_details = ado_express.get_deployment_details_from_query() + else: + user_provided_deployment_plan = deployment_plan.get_data_from_deployment_plan_file() + + if is_via_environment_latest_release_run: + with concurrent.futures.ThreadPoolExecutor() as executor: + deployment_details = executor.map(ado_express.get_deployment_detail_from_latest_release, user_provided_deployment_plan) + + return deployment_details, user_provided_deployment_plan + +def have_deployable_releases(deployment_details, user_provided_deployment_plan): + return (deployment_details is not None and any(True for _ in deployment_details)) or len(user_provided_deployment_plan) + +def initiate_search(ado_express, deployment_details, is_query_run, is_via_environment_latest_release_run, user_provided_deployment_plan): + if not is_running_as_executable(): + if is_query_run or is_via_environment_latest_release_run: + if deployment_details: + logging.info(f'Exporting Release notes to: {constants.SEARCH_RESULTS_DEPLOYMENT_PLAN_FILE_PATH}') + + ado_express.prepare_result_excel_file() + + export_results_to_excel_file(deployment_details) + else: + logging.info(f'No results found - please check the configuration') + else: + for deployment_detail in user_provided_deployment_plan: + ado_express.search_and_log_details_only(deployment_detail) + +def export_results_to_excel_file(deployment_details): + for deployment_detail in deployment_details: + if deployment_detail is not None: # The ThreadPoolExecutor may return None for some releases + row = excel_manager.convert_deployment_detail_to_excel_row(constants.DEPLOYMENT_PLAN_HEADERS, deployment_detail) + excel_manager.save_or_concat_file(row, constants.SEARCH_RESULTS_DEPLOYMENT_PLAN_FILE_PATH) + +def initiate_deployment(ado_express, deployment_details, user_provided_deployment_plan): + deployment_details = prepare_deployable_release_details(ado_express, deployment_details, user_provided_deployment_plan) + + crucial_deployment_details, regular_deployment_details = separate_regular_and_crucial_releases(ado_express, deployment_details) + + if crucial_deployment_details: deploy_crucial_releases(ado_express, crucial_deployment_details) + + if regular_deployment_details: deploy_regular_releases(ado_express, regular_deployment_details, crucial_deployment_details) + +def prepare_deployable_release_details(ado_express, deployment_details, user_provided_deployment_plan): + # When running a query/via-latest-environment-release, deployment_details will be none + deployment_details = user_provided_deployment_plan if deployment_details is None else deployment_details + + deployment_details = ado_express.updated_deployment_details_based_on_explicit_inclusion_and_exclusion(deployment_details) + + if deployment_details is None: + logging.error(f'No deployment details found - please check the configurations') + stop_process() + + return deployment_details + +def separate_regular_and_crucial_releases(ado_express, deployment_details): + crucial_release_definitions = ado_express.get_crucial_release_definitions(deployment_details) + crucial_deployment_details = [] + + if crucial_release_definitions: + # Separate crucial & regular deployments based on release definitions that match CRUCIAL_RELEASE_DEFINITIONS env variable list + crucial_deployment_details = ado_express.get_crucial_deployment_from_deployment_details(deployment_details, crucial_release_definitions) + deployment_details[:] = ado_express.remove_crucial_deployments_from_deployment_details(deployment_details, crucial_release_definitions) + + return crucial_deployment_details, deployment_details + +def deploy_crucial_releases(ado_express, crucial_deployment_details): + with concurrent.futures.ThreadPoolExecutor() as executor: + logging.info('Initiating deployment, beginning with critical releases.') + + executor.map(ado_express.deploy_to_target_or_rollback, crucial_deployment_details) + executor.shutdown(wait=True) + +def deploy_regular_releases(ado_express, deployment_details, crucial_deployment_details): + with concurrent.futures.ThreadPoolExecutor() as executor: + if crucial_deployment_details: + logging.info('Deploying the rest of the releases.') + else: + logging.info('Deploying releases.') + + executor.map(ado_express.deploy_to_target_or_rollback, deployment_details) + +def needs_deployment(target_release_number, rollback_release_number): return int(target_release_number) > int(rollback_release_number) \ No newline at end of file diff --git a/ado_express/packages/utils/__init__.py b/ado_express/packages/utils/__init__.py deleted file mode 100644 index 7e7c6e6..0000000 --- a/ado_express/packages/utils/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .asset_retrievers import * -from .excel_manager import * -from .release_note_helpers import * \ No newline at end of file diff --git a/ado_express/packages/utils/asset_retrievers/__init__.py b/ado_express/packages/utils/asset_retrievers/__init__.py deleted file mode 100644 index 5a88967..0000000 --- a/ado_express/packages/utils/asset_retrievers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .deployment_plan import * -from .release_finder import * -from .work_item_manager import * \ No newline at end of file diff --git a/ado_express/packages/utils/asset_retrievers/release_environment_finder/__init__.py b/ado_express/packages/utils/asset_retrievers/release_environment_finder/__init__.py deleted file mode 100644 index a68043c..0000000 --- a/ado_express/packages/utils/asset_retrievers/release_environment_finder/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .release_environment_finder import ReleaseEnvironmentFinder diff --git a/ado_express/packages/utils/release_manager/update_release/__init__.py b/ado_express/packages/utils/release_manager/update_release/__init__.py deleted file mode 100644 index 07f6d15..0000000 --- a/ado_express/packages/utils/release_manager/update_release/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .update_release import * \ No newline at end of file diff --git a/ado_express/packages/utils/release_note_helpers/__init__.py b/ado_express/packages/utils/release_note_helpers/__init__.py deleted file mode 100644 index 81a9721..0000000 --- a/ado_express/packages/utils/release_note_helpers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .release_note_helpers import * diff --git a/ado_express/packages/utils/release_note_helpers/release_note_helpers.py b/ado_express/packages/utils/release_note_helpers/release_note_helpers.py deleted file mode 100644 index 536e93a..0000000 --- a/ado_express/packages/utils/release_note_helpers/release_note_helpers.py +++ /dev/null @@ -1,2 +0,0 @@ -def needs_deployment(target_release_number, rollback_release_number): - return int(target_release_number) > int(rollback_release_number) \ No newline at end of file diff --git a/ado_express/tests/utils/release_manager/update_release_test.py b/ado_express/tests/utils/release_manager/update_release_test.py index 5259327..fb5b842 100644 --- a/ado_express/tests/utils/release_manager/update_release_test.py +++ b/ado_express/tests/utils/release_manager/update_release_test.py @@ -1,10 +1,15 @@ import logging -from ado_express.packages.common.enums import ReleaseEnvironmentStatuses -from ado_express.packages.common.models.deployment_details import DeploymentDetails -from ado_express.packages.utils.release_manager.update_release import UpdateRelease +import unittest + from faker import Faker from mock import patch -import unittest + +from ado_express.packages.shared.enums import ReleaseEnvironmentStatuses +from ado_express.packages.shared.models.deployment_details import \ + DeploymentDetails +from ado_express.packages.toolbox.release_manager.update_release import \ + UpdateRelease + class Empty: pass diff --git a/ado_express/tests/utils/release_note_helpers_test.py b/ado_express/tests/utils/release_note_helpers_test.py index 4c1f602..b5dde5b 100644 --- a/ado_express/tests/utils/release_note_helpers_test.py +++ b/ado_express/tests/utils/release_note_helpers_test.py @@ -1,4 +1,4 @@ -from ado_express.packages.utils.release_note_helpers import needs_deployment +from ado_express.packages.toolbox.run_helpers import needs_deployment def test_needs_deployment(): diff --git a/ado_express_api/api/deploy_views.py b/ado_express_api/api/deploy_views.py index 7198173..5410183 100644 --- a/ado_express_api/api/deploy_views.py +++ b/ado_express_api/api/deploy_views.py @@ -11,8 +11,8 @@ from rest_framework.response import Response from websocket_server.consumers import WebSocketConsumer -from ado_express.main import Startup -from ado_express.packages.common.models.deployment_status import \ +from ado_express.main import ADOExpress +from ado_express.packages.shared.models.deployment_status import \ DeploymentStatus from ado_express_api.base.models.enums.DeploymentStatusLabel import \ DeploymentStatusLabel @@ -52,7 +52,7 @@ def deploy(request): serializer.validated_data['via_env_source_name'], serializer.validated_data['deployment_details']) - ado_express = Startup(run_configurations) + ado_express = ADOExpress(run_configurations) deployment_details = [] for deployment in run_configurations.deployment_details: @@ -222,7 +222,7 @@ def retrieve_deployment_status(deployment_detail, ado_express, rollback): return latest_deployment_status -def send_live_status_data_and_check_for_failures(deployment_details, ado_express: Startup, rollback=False): +def send_live_status_data_and_check_for_failures(deployment_details, ado_express: ADOExpress, rollback=False): threads = [] failed_deployment_details = [] diff --git a/ado_express_api/api/search_views.py b/ado_express_api/api/search_views.py index 6f8cba1..11e7f63 100644 --- a/ado_express_api/api/search_views.py +++ b/ado_express_api/api/search_views.py @@ -14,7 +14,7 @@ from rest_framework.response import Response from websocket_server.consumers.consumers import WebSocketConsumer -from ado_express.main import Startup +from ado_express.main import ADOExpress from .serializers import (DeploymentDetailSerializer, DeploymentStatusSerializer, @@ -31,7 +31,7 @@ def search_via_release_environment(request): run_configurations = initialize_run_configuration( request_data, None, True, False) - ado_express = Startup(run_configurations) + ado_express = ADOExpress(run_configurations) result = process_search( ado_express, @@ -53,7 +53,7 @@ def search_via_latest_release(request): run_configurations = initialize_run_configuration( request_data, None, True, True) - ado_express = Startup(run_configurations) + ado_express = ADOExpress(run_configurations) result = process_search( ado_express, @@ -74,7 +74,7 @@ def search_via_release_number(request): run_configurations = initialize_run_configuration( request_data, None, False, False) - ado_express = Startup(run_configurations) + ado_express = ADOExpress(run_configurations) result = process_search( ado_express, @@ -96,7 +96,7 @@ def search_via_query(request): run_configurations = initialize_run_configuration( request_data, request_data['queries'], True, True) - ado_express = Startup(run_configurations) + ado_express = ADOExpress(run_configurations) result = process_search( ado_express, diff --git a/db.sqlite3 b/db.sqlite3 deleted file mode 100644 index e69de29..0000000 diff --git a/media/ui-preview.gif b/media/ui-preview.gif deleted file mode 100644 index e883276..0000000 Binary files a/media/ui-preview.gif and /dev/null differ diff --git a/package.json b/package.json index 69b6e2e..358ac03 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "scripts": { "frontend": "cd ado_express_app && npm run dev", "backend": "cross-env-shell \"cd ado_express_api && ../venv/Scripts/activate && daphne asgi:application\"", - "start": "concurrently npm:frontend npm:backend" - }, + "start webapp": "concurrently npm:frontend npm:backend" + }, "devDependencies": { "concurrently": "^8.2.0", "cross-env": "^7.0.3" } -} +} \ No newline at end of file