def download_jobs(self): download_method = pick_value( 'Would you like to download jobs by view or individually?', ['By View', 'Individually'], sort=False) if download_method: if download_method == 'By View': if self.active_connection.jenkins_views: view_options = self.active_connection.view_names all_views = '* All Views' view_options.append(all_views) view_name = pick_value( 'Which saved view would you like to download jobs for?', view_options) if view_name: if view_name == all_views: self.active_connection.download_jobs() self.active_connection.save_job_data() print( 'Successfully downloaded jobs for all views.') pause() else: self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'. format(view_name)) pause() else: if is_yes( 'No Jenkins views. Would you like to add one now?' ): self.add_view() view_name = self.active_connection.view_names[0] if is_yes('Download jobs for this view?'): self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'. format(view_name)) pause() elif download_method == 'Individually': job_name = get_input( 'Enter the exact, case-sensitive name of the job, or enter nothing to exit.\n>', lowered=False) if job_name: if self.active_connection.download_single_job(job_name): self.active_connection.save_job_data() print('Successfully downloaded Jenkins job: {}'.format( job_name)) pause() else: print('Failed to download Jenkins job: {}'.format( job_name)) pause()
def remove_view(self): if len(self.jira_views) == 0: print('No views to remove.') return to_remove = pick_value('Select a view to delete or [q]uit: ', list(self.jira_views.keys())) if to_remove is None: return # Build list of Dashboards this is going to invalidate to confirm affected_dashes = [] for dash in list(self.jira_dashboards.values()): if dash.contains_jira_view(to_remove): print( 'WARNING: Removing this view will also remove JiraDashboard: {}.' .format(dash)) affected_dashes.append(dash.name) if is_yes('Are you sure you want to delete {}?'.format(to_remove)): self.jira_views[to_remove].delete_config() del self.jira_views[to_remove] # Determine if any dashboards exist w/this view and delete them for dash_name in affected_dashes: del self.jira_dashboards[dash_name] self._save_config()
def add_connection(self) -> Optional[JenkinsConnection]: print('Enter a name for this connection, or enter nothing to exit.') connection_name = get_input('>', lowered=False) if connection_name: print('Enter a Jenkins base url.') print('Example: http://cassci.datastax.com/') url = get_input('>') auth = {} is_auth = is_yes('Does this connection require authentication?') if is_auth: auth['username'] = get_input( 'Please enter connection username\n', lowered=False) print('Please enter connection password') auth['password'] = getpass() try: jenkins_connection = JenkinsConnection(connection_name, url, auth) self.jenkins_connections[ jenkins_connection.name] = jenkins_connection self.save_jenkins_config() print('Successfully added connection: {}'.format( connection_name)) pause() return jenkins_connection except (HTTPError, MissingSchema, ConnectionError) as e: print('Error occurred adding new connection: {}'.format(e)) print('Invalid Jenkins URL. Please try again.') pause() return None
def view_cached_jobs(self): if self.active_connection.job_names: view_options = sorted(self.active_connection.view_names) all_jobs = '* All Jobs' view_options.append(all_jobs) view_name = pick_value('Which jobs would you like to view?', view_options) if view_name: if view_name == all_jobs: self.print_job_options(self.active_connection.jobs, connection=True) else: jobs_to_print = [] job_names = self.active_connection.jenkins_views[ view_name].job_names if job_names: for job_name in job_names: jobs_to_print.append( self.active_connection.jenkins_jobs[job_name]) self.print_job_options(jobs_to_print, connection=True) else: print('No jobs to print in this view.') else: if is_yes( 'There are no cached jobs for the current connection. Would you like to download jobs now?' ): self.download_jobs()
def add_view(self): view_name = pick_value('Which Jenkins view would you like to save?', self.active_connection.get_list_of_views()) if view_name: if view_name == 'Dev': dev_view_name = pick_value( 'Which Jenkins dev view would you like to save?', self.active_connection.get_list_of_views(view_name)) if dev_view_name: dev_view_name = 'Dev-{}'.format(dev_view_name) self.active_connection.jenkins_views[ dev_view_name] = self.active_connection.get_view( dev_view_name) self.active_connection.save_connection_config() view_name = dev_view_name else: self.active_connection.jenkins_views[ view_name] = self.active_connection.get_view(view_name) self.active_connection.save_connection_config() print('Successfully added view: {}'.format(view_name)) pause() if is_yes('Would you like to download jobs for this view now?'): self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'.format( view_name)) pause()
def add_custom_report_job(self): if self.connection_names: connection_name = pick_value( 'Which Jenkins Connection would you like to add jobs from?', self.connection_names) if connection_name: connection = self.jenkins_connections[connection_name] if connection_name in self.active_report.connection_names: job_options = [ job_name for job_name in connection.job_names if job_name not in self.active_report.connection_dict[connection_name] ] else: job_options = connection.job_names while True: job_name = pick_value( 'Which Jenkins job would you like to add?', job_options) if job_name: self.active_report.add_job_to_report( job_name, connection_name) self.active_report.save_report_config() print('Successfully added job: {}'.format(job_name)) job_options.remove(job_name) else: break else: if is_yes( 'No Jenkins connections to add jobs from. Would you like to add one now?' ): self.active_connection = self.add_connection() self._main_menu.go_to_jenkins_connection_menu()
def view_custom_report(self): if self.active_report.job_names: job_list = self.active_report.get_job_list(self) self.print_job_options(job_list) else: if is_yes( 'Attempted to run report with no jobs. Would you like to add jobs now?' ): self.add_custom_report_job()
def _prompt_connection_add_if_none(self) -> bool: """ :return: True if either a new connection is added or connections already exist """ if len(self._jira_connections) == 0: if is_yes( 'Did not find any JiraConnections to cache a JiraProject. Would you like to add one now?' ): self.add_connection() return True else: return False return True
def select_active_report(self): if self.jenkins_reports: report_name = pick_value( 'Which custom report would you like to open?', self.report_names) if report_name: self.active_report = self.get_custom_report(report_name) self._main_menu.go_to_jenkins_report_menu() else: if is_yes('No custom reports. Would you like to add one now?'): self.add_custom_report() self.active_report = self.get_custom_report( self.report_names[0]) self._main_menu.go_to_jenkins_report_menu()
def remove_team(self) -> Optional[str]: """ :return: Name of team that was removed, None if none. """ if len(self._teams) == 0: print('No teams currently defined.') return None to_remove = pick_value('Remove which team?', list(self._teams.keys()), True, 'Cancel') if to_remove is None: return None if is_yes('Are you sure you want to delete {}?'.format(to_remove)): del self._teams[to_remove] self._save_config() return to_remove return None
def select_active_connection(self): if self.jenkins_connections: connection_name = pick_value( 'Which Jenkins connection would you like to open?', self.connection_names) if connection_name: self.active_connection = self.get_connection(connection_name) self._main_menu.go_to_jenkins_connection_menu() else: if is_yes( 'No Jenkins connections. Would you like to add one now?'): self.add_connection() self.active_connection = self.get_connection( self.connection_names[0]) self._main_menu.go_to_jenkins_connection_menu()
def remove_connection(self): """ Removes an existing url/user/pass JIRA connection and the corresponding Jira object """ selection = pick_value('Remove which jira connection? ', list(self._jira_connections.keys()), True, 'Cancel') if selection is None: return print( 'About to delete: {}, all related views, and all offline cached JiraProject data.' .format(selection)) if is_yes('Are you sure?'): jira_connection = self._jira_connections[selection] jira_connection.delete_owned_views(self) jira_connection.delete_cached_project_data() del self._jira_connections[selection] self._save_config()
def edit_view(self) -> None: if len(self.jira_views) == 0: if is_yes('No views to edit. Would you like to add a view?'): self.add_view() else: return view_name = pick_value('Select a view to edit', list(self.jira_views.keys())) if view_name is None: return view = self.jira_views[view_name] view.edit_view(self, self.team_manager) if view.is_empty(): print('Jira View is empty. Remove it?') conf = get_input('Jira View is empty. Remove it? (q to cancel):') if conf == 'y': del self.jira_views[view_name] self._save_config()
def create_new_member_alias(self, jira_manager): # type: (JiraManager) -> None """ This linkage is performed on the logical 'Team' level rather than per JiraConnection. """ if len(self._teams) == 0: print('Must first add a team before adding a linked member.') return count = 0 for team in list(self._teams.values()): count += len(team.members) if count == 0: print( 'No members found on any teams. Add members before attempting to link members.' ) return # The linkage allows addition of >= 1 linked JiraUserName to whatever root member we want to add to. target_member = self._pick_member_for_linkage_operation('Add') changed = False while True: print('Current state of user: {}'.format(target_member)) jira_connection_to_alias = jira_manager.pick_jira_connection( 'Alias to a user account on which JIRA connection?') if jira_connection_to_alias is None: break # We have our target jira username at this point. user_to_alias_to = jira_connection_to_alias.pick_single_assignee() if user_to_alias_to is None: break new_user_alias = JiraUserName( user_to_alias_to, jira_connection_to_alias.connection_name, 'alias') target_member.add_alias(new_user_alias) changed = True if not is_yes('Add another alias?'): break if changed: self._save_config()
def add_connection( self, prompt: str = 'Name this connection:') -> Optional[JiraConnection]: """ Swallows exceptions to allow for errors during JiraProject caching w/out invalidating addition of JiraConnection """ connection_name = get_input(prompt) if is_empty(connection_name): return None url = get_input( 'JIRA url (example: http://issues.apache.org/jira/):').rstrip('/') if is_empty(url): return None user = get_input('JIRA user name:') if is_empty(user): return None password = getpass.getpass() new_jira_connection = JiraConnection(connection_name, url, user, password) self._jira_connections[ new_jira_connection.connection_name] = new_jira_connection print( 'Must locally cache at least one project\'s JIRA history. Please select a project.' ) new_jira_connection.cache_new_jira_project(self) try: while True: if not is_yes('Add another project?'): break new_jira_connection.cache_new_jira_project(self) except (JIRAError, IOError): print( 'Encountered exception processing JiraProjects. Saving base JiraConnection' ) traceback.print_exc() self._save_config() return new_jira_connection
def add_view(self) -> None: if len(self._jira_connections) == 0: if is_yes( 'No JiraConnections to add a JiraView to. Would you like to add a connection now?' ): self.add_connection() else: return None view_name = get_input('Name this view:') jira_connection_name = pick_value( 'Which JIRA Connection does this belong to?', list(self._jira_connections.keys())) if jira_connection_name is None: return new_view = JiraView(view_name, self._jira_connections[jira_connection_name]) self.jira_views[view_name] = new_view new_view.edit_view(self, self.team_manager) self._save_config()
def remove_connection(self): if self.jenkins_connections: connection_name = pick_value( 'Which Jenkins connection would you like to remove?', self.connection_names) if connection_name: print('About to remove: {}'.format(connection_name)) if is_yes('Are you sure?'): self.jenkins_connections.pop(connection_name) self.save_jenkins_config() print('Successfully removed connection: {}'.format( connection_name)) pause() else: print('Remove aborted.') pause() else: print('No Jenkins connections to remove.') pause()
def delete_cached_jira_project(self): jira_connection_name = pick_value( 'Delete cached project data for which JiraConnection?', [x.connection_name for x in self.jira_connections()]) if jira_connection_name is None: return jira_connection = self._jira_connections[jira_connection_name] project_cache_to_delete = pick_value( 'Delete cached data for which JiraProject?', jira_connection.cached_project_names) if project_cache_to_delete is None: return if is_yes('About to delete locally cached content: {}. Are you sure?'. format( jira_connection.maybe_get_cached_jira_project( project_cache_to_delete))): jira_connection.delete_cached_jira_project(project_cache_to_delete)
def build(cls, jira_views: Dict[str, JiraView]) -> Optional['JiraDashboard']: """ Links 2 or more JiraViews together, combining their results for display """ if len(jira_views) <= 1: print( 'Need at least 2 JiraViews to create a dashboard. Please create more JiraViews first.' ) return None view_name_options = list(jira_views.keys()) dash_name = get_input('Name this dashboard:', lowered=False) view_name = pick_value('Include which view?', view_name_options, True, 'Cancel') if view_name is None: return None dash_views = {view_name: jira_views[view_name]} view_name_options.remove(view_name) view_name = pick_value('Second view?', view_name_options, False) # make mypy happy - doesn't realize False means we can't have None if view_name is None: return None dash_views[view_name] = jira_views[view_name] view_name_options.remove(view_name) while len(view_name_options) > 0: if is_yes('Add another?'): view_name = pick_value('What view?', view_name_options, True, '[q] Cancel') if view_name == 'q' or view_name is None: break dash_views[view_name] = jira_views[view_name] view_name_options.remove(view_name) else: break return JiraDashboard(dash_name, dash_views)
def remove_view(self): if self.active_connection.jenkins_views: view_name = pick_value( 'Which Jenkins view would you like to remove?', self.active_connection.view_names) if view_name: print('About to delete: {}'.format(view_name)) if is_yes('Are you sure?'): del self.active_connection.jenkins_views[view_name] self.active_connection.save_connection_config() file_name = os.path.join(jenkins_views_dir, "{}.cfg".format(view_name)) if os.path.exists(file_name): os.remove(file_name) print('{} view deleted.'.format(view_name)) else: print('Attempted to remove view {}'.format(view_name)) print('View path {} does not exist'.format(file_name)) else: print('Remove aborted.') pause() else: print('No Jenkins views to remove.')
def report_fix_version(self) -> None: """ Creates a report of all tickets, including dependencies, to the input FixVersion. """ # Only support creating of this on a single JiraConnection, with the assumption that multiple projects on that # connection can share a FixVersion, but these won't straddle to exterior Jira instances target_connection = self.pick_jira_connection( 'FixVersion report for which JiraConnection?') if target_connection is None: return open_only = is_yes('Show only unresolved issues?') to_match = get_input('Input substring to search fixversions for:', False) available_versions = set() for jira_project in target_connection.cached_projects: for jira_issue in jira_project.jira_issues.values(): for fix in jira_issue['fixVersions'].split(','): if to_match in fix: available_versions.add(fix) report_version = pick_value('Generate report for which FixVersion?', list(available_versions)) if report_version is None: return print('Generating report on: {}'.format(report_version)) # Now find all "primary root" members on this FixVersion, generate a list of matching, then display w/dependency # chains enabled matching_issues = set() for jira_project in target_connection.cached_projects: for jira_issue in jira_project.jira_issues.values(): if jira_issue.has_fix_version(report_version): if (open_only and jira_issue.is_open) or not open_only: matching_issues.add(jira_issue) df = DisplayFilter.default() df.open_only = open_only df.include_column('fixVersions', 'FixVersion', 10, 2) # sort our keys by issuekey sorted_results = JiraUtils.sort_custom_jiraissues_by_key( list(matching_issues)) del matching_issues issues = df.display_and_return_sorted_issues(self, sorted_results, 1, None, True) while True: choice = get_input( '[#] to open an issue in browser, [p] to print report again, [q] to quit report: ' ) if choice == 'q': break elif choice == 'p': df.display_and_return_sorted_issues(self, sorted_results, 1, None, True) try: int_choice = int(choice) - 1 if int_choice < 0 or int_choice > len(issues) - 1: raise ValueError('oops') chosen_issue = issues[int_choice] if not chosen_issue.is_cached: print( 'Cannot open browser for non-cached issue (don\'t know url). Cache offline to inspect {}.' .format(chosen_issue.issue_key)) else: jira_conn = self._jira_connections[ chosen_issue.jira_connection_name] JiraUtils.open_issue_in_browser(jira_conn.url, chosen_issue.issue_key) except ValueError: print('Bad input. Try again.')
def __init__(self, team_manager): """ Recreates any JiraConnections and JiraViews based on saved data in conf/jira.cfg """ # Holds connected Jira objects to be queried by JiraViews self._jira_connections = {} # type: Dict[str, JiraConnection] # JiraViews, caching filters for different ways to view Jira Data. Implicit 1:1 JiraConnection to JiraView self.jira_views = {} # type: Dict[str, JiraView] self.jira_dashboards = {} # type: Dict[str, JiraDashboard] # Used during JiraDependency resolution to notify user of missing JiraProjects self.missing_project_counts = {} # type: Dict[str, int] self._display_filter = DisplayFilter.default() if os.path.exists(jira_conf_file): config_parser = configparser.RawConfigParser() config_parser.read(jira_conf_file) connection_names = [] if config_parser.has_section( 'JiraManager') and config_parser.has_option( 'JiraManager', 'Connections'): connection_names = config_parser.get('JiraManager', 'Connections').split(',') # JiraConnections are the root of our container hierarchy for connection_name in connection_names: if connection_name == '': pass try: jira_connection = JiraConnection.from_file(connection_name) # If we had an error on init we obviously cannot add this if jira_connection is None: continue self._jira_connections[ jira_connection.connection_name] = jira_connection except ConfigError as ce: print('ConfigError with project {}: {}'.format( connection_name, ce)) # Construct JiraViews so they can be used during JiraDashboard creation. view_names = [] if config_parser.has_option('JiraManager', 'Views'): view_names = config_parser.get('JiraManager', 'Views').split(',') for name in view_names: try: jv = JiraView.from_file(self, name, team_manager) self.jira_views[jv.name] = jv except ConfigError as ce: print('ConfigError with jira view {}: {}'.format(name, ce)) if config_parser.has_section('Dashboards'): for dash in config_parser.options('Dashboards'): dash_views = {} for view in view_names: if view not in self.jira_views: print( 'Found dashboard {} with invalid view: {}. Skipping init: manually remove from config.' .format(dash, view)) break dash_views[view] = self.jira_views[view] self.jira_dashboards[dash] = JiraDashboard( dash, dash_views) if len(self._jira_connections) == 0: print_separator(30) print( 'No JIRA Connections found. Prompting to add first connection.' ) self.add_connection() # Initialize JiraProjects from locally cached files for file_name in os.listdir(jira_project_dir): full_path = os.path.join(jira_project_dir, file_name) print( 'Processing locally cached JiraProject: {}'.format(full_path)) # Init based on matching the name of this connection and .cfg print_separator(30) try: new_jira_project = JiraProject.from_file(full_path, self) if new_jira_project is None: print('Error initializing from {}. Skipping'.format( full_path)) break if new_jira_project.jira_connection is None: add = get_input( 'Did not find JiraConnection for JiraProject: {}. Would you like to add one now? (y/n)' ) if add == 'y': new_jira_connection = self.add_connection( 'Name the connection (reference url: {}):'.format( new_jira_project.url)) new_jira_connection.save_config() new_jira_project.jira_connection = new_jira_connection else: print( 'Did not add JiraConnection, so cannot link and use JiraProject.' ) continue print('Updating with new data from JIRA instance') new_jira_project.refresh() new_jira_project.jira_connection.add_and_link_jira_project( new_jira_project) except (configparser.NoSectionError, ConfigError) as e: print( 'WARNING! Encountered error initializing JiraProject from file {}: {}' .format(full_path, e)) print( 'This JiraProject will not be initialized. Remove it manually from disk in conf/jira/projects and data/jira/' ) if os.path.exists('conf/custom_params.cfg'): config_parser = configparser.RawConfigParser() config_parser.read('conf/custom_params.cfg') custom_projects = config_parser.get('CUSTOM_PROJECTS', 'project_names').split(',') for project_name in custom_projects: argus_debug( 'Processing immutable config for custom project: {}'. format(project_name)) # Find the JiraConnection w/matching URL, if any url = config_parser.get(project_name, 'url').rstrip('/') jira_project = self.maybe_get_cached_jira_project( url, project_name) if jira_project is not None: # Don't need to cache since already done on ctor for JiraProject argus_debug('Project already initialized. Skipping.') continue # Didn't find the JiraProject, so we need to build one, cache, and link. custom_fields = {} field_names = config_parser.get(project_name, 'custom_fields').split(',') for field in field_names: custom_fields[field] = config_parser.get( project_name, field) parent_jira_connection = None for jira_connection in list(self._jira_connections.values()): if jira_connection.url == url: parent_jira_connection = jira_connection break # Create a JiraConnection for this JiraProject if we do not yet have one if parent_jira_connection is None: print( 'WARNING! Did not find JiraConnection for project: {}, attempting to match url: {}' .format(project_name, url)) print('Known JiraConnections and their urls:') for jira_connection in list( self._jira_connections.values()): print(' {}: {}'.format( jira_connection.connection_name, jira_connection.url)) if is_yes('Would you like to add one now?'): parent_jira_connection = self.add_connection( 'Name the connection (reference url: {}):'.format( url)) else: print( 'JiraProject data and config will not be added nor cached. Either add it manually or restart Argus and reply y' ) break new_jira_project = JiraProject(parent_jira_connection, project_name, url, custom_fields) new_jira_project.refresh() parent_jira_connection.add_and_link_jira_project( new_jira_project) print('Resolving dependencies between JiraIssues') self._resolve_issue_dependencies() print('JiraManager initialization complete.')