Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeOwnersParser Team/User lookup #6366

Merged
merged 4 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static class Program
/// Defaults to ".git".
/// Example usage: ".git|foo|bar"
/// </param>
/// <param name="teamStorageURI">Override for the default URI where the team/storage blob data resides</param>
/// <returns>
/// On STDOUT: The JSON representation of the matched CodeownersEntry.
/// "new CodeownersEntry()" if no path in the CODEOWNERS data matches.
Expand All @@ -48,7 +49,8 @@ public static int Main(
string codeownersFilePathOrUrl,
bool excludeNonUserAliases = false,
string? targetDir = null,
string ignoredPathPrefixes = DefaultIgnoredPrefixes)
string ignoredPathPrefixes = DefaultIgnoredPrefixes,
string? teamStorageURI = null)
{
try
{
Expand All @@ -71,11 +73,13 @@ public static int Main(
targetDir!,
codeownersFilePathOrUrl,
excludeNonUserAliases,
SplitIgnoredPathPrefixes())
SplitIgnoredPathPrefixes(),
teamStorageURI)
: GetCodeownersForSimplePath(
targetPath,
codeownersFilePathOrUrl,
excludeNonUserAliases);
excludeNonUserAliases,
teamStorageURI);

string codeownersJson = JsonSerializer.Serialize(
codeownersData,
Expand All @@ -101,7 +105,8 @@ private static Dictionary<string, CodeownersEntry> GetCodeownersForGlobPath(
string targetDir,
string codeownersFilePathOrUrl,
bool excludeNonUserAliases,
string[]? ignoredPathPrefixes = null)
string[]? ignoredPathPrefixes = null,
string? teamStorageURI=null)
{
ignoredPathPrefixes ??= Array.Empty<string>();

Expand All @@ -110,7 +115,8 @@ private static Dictionary<string, CodeownersEntry> GetCodeownersForGlobPath(
targetPath,
targetDir,
codeownersFilePathOrUrl,
ignoredPathPrefixes);
ignoredPathPrefixes,
teamStorageURI);

if (excludeNonUserAliases)
codeownersEntries.Values.ToList().ForEach(entry => entry.ExcludeNonUserAliases());
Expand All @@ -121,12 +127,14 @@ private static Dictionary<string, CodeownersEntry> GetCodeownersForGlobPath(
private static CodeownersEntry GetCodeownersForSimplePath(
string targetPath,
string codeownersFilePathOrUrl,
bool excludeNonUserAliases)
bool excludeNonUserAliases,
string? teamStorageURI = null)
{
CodeownersEntry codeownersEntry =
CodeownersFile.GetMatchingCodeownersEntry(
targetPath,
codeownersFilePathOrUrl);
codeownersFilePathOrUrl,
teamStorageURI);

if (excludeNonUserAliases)
codeownersEntry.ExcludeNonUserAliases();
Expand Down
24 changes: 0 additions & 24 deletions tools/code-owners-parser/CodeOwnersManualTester/CodeOwnerUtils.cs

This file was deleted.

This file was deleted.

38 changes: 0 additions & 38 deletions tools/code-owners-parser/CodeOwnersManualTester/Program.cs

This file was deleted.

6 changes: 0 additions & 6 deletions tools/code-owners-parser/CodeOwnersParser.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Sdk.Tools.CodeOwnersParser.Tests", "Azure.Sdk.Tools.CodeOwnersParser.Tests\Azure.Sdk.Tools.CodeOwnersParser.Tests.csproj", "{66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeOwnersManualTester", "CodeOwnersManualTester\CodeOwnersManualTester.csproj", "{4CDD76F7-4EFD-42A1-B39F-DB1728964ABF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,10 +41,6 @@ Global
{66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66C9FF6A-32DD-4C3C-ABE1-F1F58C1C8129}.Release|Any CPU.Build.0 = Release|Any CPU
{4CDD76F7-4EFD-42A1-B39F-DB1728964ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CDD76F7-4EFD-42A1-B39F-DB1728964ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CDD76F7-4EFD-42A1-B39F-DB1728964ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CDD76F7-4EFD-42A1-B39F-DB1728964ABF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
70 changes: 22 additions & 48 deletions tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Azure.Sdk.Tools.CodeOwnersParser
{
Expand Down Expand Up @@ -45,7 +48,7 @@ public CodeownersEntry(string pathExpression, List<string> owners)
}

private static string[] SplitLine(string line, char splitOn)
=> line.Split(new char[] { splitOn }, StringSplitOptions.RemoveEmptyEntries);
=> line.Split(new char[] { splitOn }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

public override string ToString()
=> $"HasWildcard:{ContainsWildcard} Expression:{PathExpression} " +
Expand Down Expand Up @@ -85,14 +88,11 @@ private static IEnumerable<string> ParseLabels(string line, string moniker)
line = line[(colonPosition + 1)..].Trim();
foreach (string label in SplitLine(line, LabelSeparator).ToList())
{
if (!string.IsNullOrWhiteSpace(label))
{
yield return label.Trim();
}
yield return label;
}
}

public void ParseOwnersAndPath(string line)
public void ParseOwnersAndPath(string line, TeamUserHolder teamUserHolder)
{
if (
string.IsNullOrEmpty(line)
Expand All @@ -114,31 +114,29 @@ public void ParseOwnersAndPath(string line)
{
foreach (string author in SplitLine(line, OwnerSeparator).ToList())
{
if (!string.IsNullOrWhiteSpace(author))
// If the author is a team, get the user list and add that to the Owners
if (!IsGitHubUserAlias(author))
{
// If the author is a team, get the user list and add that to the Owners
if (!IsGitHubUserAlias(author))
var teamUsers = teamUserHolder.GetUsersForTeam(author);
// If the team is found in team user data, add the list of users to
// the owners and ensure the end result is a distinct list
if (teamUsers.Count > 0)
{
var teamUsers = GetUsersForTeam(author.Trim());
// If the team is found in team user data, add the list of users to
// the owners and ensure the end result is a distinct list
if (teamUsers.Count > 0)
{
// The union of the two lists will ensure the result a distinct list
Owners = Owners.Union(teamUsers).ToList();
}
// Else, the team user data did not contain an entry or there were no user
// for the team. In that case, just add the team to the list of authors
else
{
Owners.Add(author);
}
// The union of the two lists will ensure the result a distinct list
Owners = Owners.Union(teamUsers).ToList();
}
// Else, the team user data did not contain an entry or there were no user
// for the team. In that case, just add the team to the list of authors
else
{
Owners.Add(author.Trim());
Owners.Add(author);
}
}
// If the entry isn't a team, then just add it
else
{
Owners.Add(author);
}
}
}
else
Expand All @@ -147,30 +145,6 @@ public void ParseOwnersAndPath(string line)
}
}

private static List<string> GetUsersForTeam(string teamName)
{
// The teamName in the codeowners file should be in the form <org>/<team>.
// The dictionary's team names do not contain the org so the org needs to
// be stripped off. Handle the case where the teamName passed in does and
// does not being with @org/
string teamWithoutOrg = teamName.Trim();
if (teamName.Contains('/'))
{
teamWithoutOrg = teamName.Split("/")[1].Trim();
}
var teamUserDict = CodeownersFile.GetTeamUserData();
if (teamUserDict != null)
{
if (teamUserDict.ContainsKey(teamWithoutOrg))
{
Console.WriteLine($"Found team entry for {teamWithoutOrg}");
return teamUserDict[teamWithoutOrg];
}
Console.WriteLine($"Warning: TeamUserDictionary did not contain a team entry for {teamWithoutOrg}");
}
return new List<string>();
}

private static bool IsComment(string line)
=> line.StartsWith("#");

Expand Down
53 changes: 15 additions & 38 deletions tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,17 @@ namespace Azure.Sdk.Tools.CodeOwnersParser
public static class CodeownersFile
{

private static string teamUserBlobUri = $"https://{StorageConstants.AzureBlobAccountName}.blob.core.windows.net/{StorageConstants.AzureSdkWriteTeamsContainer}/{StorageConstants.AzureSdkWriteTeamsBlobName}";
private static Dictionary<string, List<string>>? teamUserDict = null;
public static List<CodeownersEntry> GetCodeownersEntriesFromFileOrUrl(
string codeownersFilePathOrUrl)
string codeownersFilePathOrUrl,
string? teamStorageURI = null)
{
string content = FileHelpers.GetFileOrUrlContents(codeownersFilePathOrUrl);
return GetCodeownersEntries(content);
return GetCodeownersEntries(content, teamStorageURI);
}

public static Dictionary<string, List<string>>?GetTeamUserData()
{
if (null == teamUserDict)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();

string rawJson = FileHelpers.GetFileOrUrlContents(teamUserBlobUri);
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;

// Format and display the TimeSpan value.
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine($"Time to pull teamUserBlob: {elapsedTime}");
var list = JsonSerializer.Deserialize<List<KeyValuePair<string, List<string>>>>(rawJson);
if (null != list)
{
teamUserDict = list.ToDictionary((keyItem) => keyItem.Key, (valueItem) => valueItem.Value);
}
}
return teamUserDict;
}

public static List<CodeownersEntry> GetCodeownersEntries(string codeownersContent)
public static List<CodeownersEntry> GetCodeownersEntries(string codeownersContent, string? teamStorageURI = null)
{
TeamUserHolder teamUserHolder = new TeamUserHolder(teamStorageURI);
List<CodeownersEntry> entries = new List<CodeownersEntry>();

// We are going to read line by line until we find a line that is not a comment
Expand All @@ -58,29 +32,31 @@ public static List<CodeownersEntry> GetCodeownersEntries(string codeownersConten
using StringReader sr = new StringReader(codeownersContent);
while (sr.ReadLine() is { } line)
{
entry = ProcessCodeownersLine(line, entry, entries);
entry = ProcessCodeownersLine(line, entry, entries, teamUserHolder);
}

return entries;
}

public static CodeownersEntry GetMatchingCodeownersEntry(
string targetPath,
string codeownersFilePathOrUrl)
string codeownersFilePathOrUrl,
string? teamStorageURI = null)
{
var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl);
var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl, teamStorageURI);
return GetMatchingCodeownersEntry(targetPath, codeownersEntries);
}

public static Dictionary<string, CodeownersEntry> GetMatchingCodeownersEntries(
GlobFilePath targetPath,
string targetDir,
string codeownersFilePathOrUrl,
string[]? ignoredPathPrefixes = null)
string[]? ignoredPathPrefixes = null,
string? teamStorageURI = null)
{
ignoredPathPrefixes ??= Array.Empty<string>();

var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl);
var codeownersEntries = GetCodeownersEntriesFromFileOrUrl(codeownersFilePathOrUrl, teamStorageURI);

Dictionary<string, CodeownersEntry> codeownersEntriesByPath = targetPath
.ResolveGlob(targetDir, ignoredPathPrefixes)
Expand All @@ -104,7 +80,8 @@ public static CodeownersEntry GetMatchingCodeownersEntry(
private static CodeownersEntry ProcessCodeownersLine(
string line,
CodeownersEntry entry,
List<CodeownersEntry> entries)
List<CodeownersEntry> entries,
TeamUserHolder teamUserHolder)
{
line = NormalizeLine(line);

Expand All @@ -115,7 +92,7 @@ private static CodeownersEntry ProcessCodeownersLine(

if (!IsCommentLine(line) || (IsCommentLine(line) && IsPlaceholderEntry(line)))
{
entry.ParseOwnersAndPath(line);
entry.ParseOwnersAndPath(line, teamUserHolder);

if (entry.IsValid)
entries.Add(entry);
Expand Down
Loading