def validate_mappings(options, args): """Ensure team/component mapping remains consistent after patch. The main purpose of this check is to notify the user if any edited (or added) team tag makes a component map to multiple teams. Args: options: Command line options from optparse args: List of paths to affected OWNERS files Returns: A string containing the details of any multi-team per component. """ mappings_file = json.load(urllib2.urlopen(options.current_mapping_url)) new_dir_to_component = mappings_file.get('dir-to-component', {}) new_dir_to_team = mappings_file.get('dir-to-team', {}) affected = {} deleted = [] affected_components = set() # Parse affected OWNERS files for f in args: rel, full = rel_and_full_paths(options.root, f) if os.path.exists(full): affected[os.path.dirname(rel)] = parse(full) else: deleted.append(os.path.dirname(rel)) # Update component mapping with current changes. for rel_path, tags in affected.iteritems(): component = tags.get('component') team = tags.get('team') os_tag = tags.get('os') if component: if os_tag: component = '%s(%s)' % (component, os_tag) new_dir_to_component[rel_path] = component affected_components.add(component) elif rel_path in new_dir_to_component: del new_dir_to_component[rel_path] if team: new_dir_to_team[rel_path] = team elif rel_path in new_dir_to_team: del new_dir_to_team[rel_path] for deleted_dir in deleted: if deleted_dir in new_dir_to_component: del new_dir_to_component[deleted_dir] if deleted_dir in new_dir_to_team: del new_dir_to_team[deleted_dir] # For the components affected by this patch, compute the directories that map # to it. affected_component_to_dirs = {} for d, component in new_dir_to_component.iteritems(): if component in affected_components: affected_component_to_dirs.setdefault(component, []) affected_component_to_dirs[component].append(d) # Convert component->[dirs], dir->team to component->[teams]. affected_component_to_teams = { component: list(set([new_dir_to_team[d] for d in dirs if d in new_dir_to_team])) for component, dirs in affected_component_to_dirs.iteritems() } # Perform cardinality check. warnings = '' for component, teams in affected_component_to_teams.iteritems(): if len(teams) > 1: warnings += '\nComponent %s will map to %s' % (component, ', '.join(teams)) if warnings: warnings = ( 'Are you sure these are correct? After landing this patch:%s' % warnings) return warnings
def validate_mappings(options, args): """Ensure team/component mapping remains consistent after patch. The main purpose of this check is to prevent new and edited OWNERS files introduce multiple teams for the same component. Args: options: Command line options from optparse args: List of paths to affected OWNERS files """ mappings_file = json.load(urllib2.urlopen(options.current_mapping_url)) # Convert dir -> component, component -> team to dir -> (team, component) current_mappings = {} for dir_name in mappings_file['dir-to-component'].keys(): component = mappings_file['dir-to-component'].get(dir_name) if component: team = mappings_file['component-to-team'].get(component) else: team = None current_mappings[dir_name] = (team, component) # Extract dir -> (team, component) for affected files affected = {} deleted = [] for f in args: rel, full = rel_and_full_paths(options.root, f) if os.path.exists(full): affected[os.path.dirname(rel)] = parse(full) else: deleted.append(os.path.dirname(rel)) for d in deleted: current_mappings.pop(d, None) current_mappings.update(affected) #Ensure internal consistency of modified mappings. new_dir_to_component = {} new_component_to_team = {} team_to_dir = defaultdict(list) errors = {} for dir_name, tags in current_mappings.iteritems(): team, component = tags if component: new_dir_to_component[dir_name] = component if team: team_to_dir[team].append(dir_name) if component and team: if new_component_to_team.setdefault(component, team) != team: if component not in errors: errors[component] = set( [new_component_to_team[component], team]) else: errors[component].add(team) result = [] for component, teams in errors.iteritems(): error_message = 'The component "%s" has more than one team: ' % component team_details = [] for team in teams: team_details.append('%(team)s is used in %(paths)s' % { 'team': team, 'paths': ', '.join(team_to_dir[team]), }) error_message += '; '.join(team_details) result.append({ 'error': error_message, 'full_path': ' '.join([ '%s/OWNERS' % d for d, c in new_dir_to_component.iteritems() if c == component and d in affected.keys() ]) }) return result