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
24 changes: 24 additions & 0 deletions tools/code-owners-parser/CodeOwnersManualTester/CodeOwnerUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Sdk.Tools.CodeOwnersParser;

namespace CodeOwnersManualTester
{
internal class CodeOwnerUtils
{
/// <summary>
/// Wrapper function to load the CODEOWNERS file from a given path or URL and return
/// the list of codeowners entries.
/// </summary>
/// <param name="codeOwnersFilePath"></param>
/// <returns>List of CodeownersEntry</returns>
public static List<CodeownersEntry> GetCodeOwnerEntries(string codeOwnersFilePath)
{
Console.WriteLine($"Loading codeowners file, {codeOwnersFilePath}");
return CodeownersFile.GetCodeownersEntriesFromFileOrUrl(codeOwnersFilePath);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CodeOwnersParser\Azure.Sdk.Tools.CodeOwnersParser.csproj" />
</ItemGroup>
</Project>
38 changes: 38 additions & 0 deletions tools/code-owners-parser/CodeOwnersManualTester/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Azure.Sdk.Tools.CodeOwnersParser;

namespace CodeOwnersManualTester
{
internal class Program
{
static void Main(string[] args)
{
// Multiple repository list
List<string> repositoryList = new List<string>
{
"azure-sdk",
"azure-sdk-tools",
"azure-sdk-for-android",
"azure-sdk-for-c",
"azure-sdk-for-cpp",
"azure-sdk-for-go",
"azure-sdk-for-java",
"azure-sdk-for-js",
"azure-sdk-for-net",
"azure-sdk-for-python"
};
// Single Repository List
//List<string> repositoryList = new List<string>
//{
// "azure-sdk-for-java"
//};
foreach (string repository in repositoryList)
{
string codeownersUrl = $"https://raw.githubusercontent.com/Azure/{repository}/main/.github/CODEOWNERS";
List<CodeownersEntry> coEntries = CodeOwnerUtils.GetCodeOwnerEntries(codeownersUrl);
Console.WriteLine($"Total number of Codeowner entries: {coEntries.Count}");
}
// Something to break on
Console.WriteLine("done");
}
}
}
6 changes: 6 additions & 0 deletions tools/code-owners-parser/CodeOwnersParser.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ 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 @@ -41,6 +43,10 @@ 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
64 changes: 61 additions & 3 deletions tools/code-owners-parser/CodeOwnersParser/CodeownersEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,69 @@ public void ParseOwnersAndPath(string line)
line = ParsePath(line);
line = RemoveCommentIfAny(line);

foreach (string author in SplitLine(line, OwnerSeparator).ToList())
// If the line doesn't contain the OwnerSeparator AKA no owners, then the foreach loop below
// won't work. For example, the following line would end up causing "/sdk/communication" to
// be added as an owner when one is not listed
// /sdk/communication/
if (line.Contains(OwnerSeparator))
{
if (!string.IsNullOrWhiteSpace(author))
Owners.Add(author.Trim());
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))
{
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);
}
}
else
{
Owners.Add(author.Trim());
}
}
}
}
else
{
Console.WriteLine($"Warning: CODEOWNERS line '{line}' does not have an owner entry.");
}
}

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)
Expand Down
30 changes: 30 additions & 0 deletions tools/code-owners-parser/CodeOwnersParser/CodeownersFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,48 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;

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 content = FileHelpers.GetFileOrUrlContents(codeownersFilePathOrUrl);
return GetCodeownersEntries(content);
}

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)
{
List<CodeownersEntry> entries = new List<CodeownersEntry>();
Expand Down
15 changes: 15 additions & 0 deletions tools/code-owners-parser/CodeOwnersParser/StorageConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Azure.Sdk.Tools.CodeOwnersParser
{
internal class StorageConstants
{
public const string AzureBlobAccountName = "azuresdkartifacts";
public const string AzureSdkWriteTeamsContainer = "azure-sdk-write-teams";
public const string AzureSdkWriteTeamsBlobName = "azure-sdk-write-teams-blob";
}
}