Skip to content

Commit

Permalink
Migrate most of the logic that doesn't depend on CLI to a separate sh…
Browse files Browse the repository at this point in the history
…ared library (microsoft#295)

* Moved shared logic that isn't relevant to the CLI to Shared-lib. This way the libraries can access that logic and not be dependent on CLI Specific logic.

* Update Find Squats to use the new Shared and Shared-lib structure.

* Fix the FindSourceTool to use the new lib stuff.

* Update the rest of the OSS Gadget tools to use the HTTP Client from HttpClientFactory instead of the CommonInitialization WebClient.
Still need to fix the tests.

* Removed import of shared in oss-download that was causing problems for it.

* Add support for old behavior of single static httpclient when none is provided.

Fixes shared lib nuget.

* Namespaces cleanup

Fix no default parametereless constructor for gadgets.

* Remove Unneeded Usings

The usings were causing the default static HttpHandler to become disposed prematurely.

Restore ENvironment variable setting to baseprojectmanager.

* Expand PyPi source finding code.

* Fix CodeQL Workflow for .NET 6

* Allow empty construction of DefaultHttpClient

* Use DefaultHttpClientFactory for CLIs

Update DefaultHttpClientFactory to use the environment variable for the user agent.

* Remove CommonInitialization

After removal of the static reference to httpclient this no longer did anything.

* Restore parameterless constructors for CLI objects

These constructors call into the IHTTPClientFactory constructor with DefaultHttpClientFactory.

* Second set of constructors

* Use the restored parameterless constructors.

Co-authored-by: Gabe Stocco <[email protected]>
  • Loading branch information
jpinz and gfs authored Jan 21, 2022
1 parent b872870 commit a25747d
Show file tree
Hide file tree
Showing 96 changed files with 1,869 additions and 1,118 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ jobs:
fetch-depth: 0

- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1.7.2
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
dotnet-version: 6.0.x

- name: Initialize CodeQL
uses: github/codeql-action/init@v1
Expand Down
14 changes: 10 additions & 4 deletions src/OSSGadget.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29806.167
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{292BEE61-1C30-4EB5-A2DE-810E7118433A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared.CLI", "Shared\Shared.CLI.csproj", "{292BEE61-1C30-4EB5-A2DE-810E7118433A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oss-download", "oss-download\oss-download.csproj", "{DEBC05E9-A5A9-4FFE-9023-1248F2DA58D9}"
EndProject
Expand Down Expand Up @@ -38,7 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oss-find-domain-squats", "o
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oss-reproducible", "oss-reproducible\oss-reproducible.csproj", "{B79FCA22-5A3B-47F5-8FB1-692E3175162D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "oss-find-squats-lib", "oss-find-squats-lib\oss-find-squats-lib.csproj", "{182CA72B-6BB1-400E-83FC-B35558928BAB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oss-find-squats-lib", "oss-find-squats-lib\oss-find-squats-lib.csproj", "{182CA72B-6BB1-400E-83FC-B35558928BAB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared.Lib", "Shared-lib\Shared.Lib.csproj", "{E87DAFE2-A060-458F-8924-C946585BF746}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -109,6 +111,10 @@ Global
{182CA72B-6BB1-400E-83FC-B35558928BAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{182CA72B-6BB1-400E-83FC-B35558928BAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{182CA72B-6BB1-400E-83FC-B35558928BAB}.Release|Any CPU.Build.0 = Release|Any CPU
{E87DAFE2-A060-458F-8924-C946585BF746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E87DAFE2-A060-458F-8924-C946585BF746}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E87DAFE2-A060-458F-8924-C946585BF746}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E87DAFE2-A060-458F-8924-C946585BF746}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
48 changes: 48 additions & 0 deletions src/Shared-lib/DefaultHttpClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

namespace Microsoft.CST.OpenSource
{
using Microsoft.CST.OpenSource.Helpers;
using System;
using System.Net.Http;

/// <summary>
/// This is the HttpClientFactory that will be used if one is not specified. This factory lazily constructs a single HttpClient and presents it for reuse to reduce resource usage.
/// </summary>
public sealed class DefaultHttpClientFactory : IHttpClientFactory
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Modified through reflection.")]
private string ENV_HTTPCLIENT_USER_AGENT = "OSSDL";

public DefaultHttpClientFactory(string? userAgent = null)
{
EnvironmentHelper.OverrideEnvironmentVariables(this);

_httpClientLazy = new(() =>
{
HttpClient cli = new(handler);
cli.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent ?? ENV_HTTPCLIENT_USER_AGENT);
return cli;
});
}

private static readonly SocketsHttpHandler handler = new()
{
AllowAutoRedirect = true,
UseCookies = false,
MaxAutomaticRedirections = 5,
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
PooledConnectionLifetime = TimeSpan.FromSeconds(30),
AutomaticDecompression = System.Net.DecompressionMethods.All,
};

private readonly Lazy<HttpClient> _httpClientLazy;

/// <summary>
/// Returns the singleton HttpClient for this factory.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public HttpClient CreateClient(string name) => _httpClientLazy.Value;
}
}
20 changes: 20 additions & 0 deletions src/Shared-lib/Exceptions/InvalidProjectManagerException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

namespace Microsoft.CST.OpenSource.Exceptions
{
using System;

/// <summary>
/// Exception thrown when the PackageURL has an invalid manager..
/// </summary>
public class InvalidProjectManagerException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="InvalidProjectManagerException"/> class.
/// </summary>
public InvalidProjectManagerException(PackageURL packageUrl)
: base($"The package URL: {packageUrl} has an invalid Project Manager.")
{
}
}
}
127 changes: 127 additions & 0 deletions src/Shared-lib/Helpers/Check.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

namespace Microsoft.CST.OpenSource.Helpers
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Static helper methods for validating parameters.
/// </summary>
public static class Check
{
/// <summary>
/// Checks that the given parameter is not null, empty, or whitespace.
/// </summary>
/// <param name="paramName">The name of the parameter. Can be retrieved by using nameof(parameter).</param>
/// <param name="value">The value of the parameter.</param>
/// <returns>The value of the parameter if it passes the validation.</returns>
/// <exception cref="ArgumentNullException">The parameter value is null, empty, or whitespace.</exception>
public static string NotEmptyOrWhitespace(string paramName, string? value)
{
// The parameterName can't be null so explicitly check (can't call EnsureIsNotNullOrWhitespace in this case!)
if (string.IsNullOrWhiteSpace(paramName))
{
throw new ArgumentNullException(nameof(paramName));
}

if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentNullException(value);
}

return value;
}

/// <summary>
/// Checks that the given parameter is not null.
/// </summary>
/// <typeparam name="T">The type of the parameter.</typeparam>
/// <param name="paramName">The name of the parameter. Can be retrieved by using nameof(parameter).</param>
/// <param name="value">The value of the parameter.</param>
/// <param name="message">An optional message that will be included as apart of the exception if the value is null.</param>
/// <returns>The value of the parameter if it passes the validation.</returns>
/// <exception cref="ArgumentNullException">The parameter value is null.</exception>
public static T NotNull<T>(string paramName, T? value, string? message = null)
{
NotEmptyOrWhitespace(nameof(paramName), paramName);
message = message ?? $"Value cannot be null. (Parameter '{paramName}')";

if (value == null)
{
throw new ArgumentNullException(paramName, message);
}

return value;
}

/// <summary>
/// Check that the given parameter is in the enum.
/// </summary>
/// <typeparam name="T">The enum type of the parameter.</typeparam>
/// <param name="paramName">The name of the parameter. Can be retrieved by using nameof(parameter).</param>
/// <param name="value">The value of the parameter.</param>
/// <returns>The value of the parameter if it passes the validation.</returns>
/// <exception cref="ArgumentOutOfRangeException">The parameter value is not apart of the enum.</exception>
public static T IsInEnum<T>(string paramName, T value)
where T : Enum
{
NotEmptyOrWhitespace(nameof(paramName), paramName);
NotNull(paramName, value);

if (!Enum.IsDefined(value.GetType(), value))
{
throw new ArgumentOutOfRangeException(
paramName,
$"The value is {value} is not a valid value of {value.GetType()}.");
}

return value;
}

/// <summary>
/// Ensures that the provided enumerable is not null and contains at least one item.
/// </summary>
/// <typeparam name="T">The type of object contained in the enumerable.</typeparam>
/// <param name="parameterName">Parameter name (embedded in exception if thrown).</param>
/// <param name="parameterValue">The value to validate.</param>
/// <exception cref="ArgumentNullException"> Enumerable object is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Enumerable does not contain at least 1 object.</exception>
public static void NotNullOrEmpty<T>(
string parameterName,
IEnumerable<T>? parameterValue)
{
NotNullAndHasMinimumCount(parameterName, parameterValue, 1);
}

/// <summary>
/// Ensures that the provided enumerable is not null and contains at least minimumCount elements.
/// </summary>
/// <typeparam name="T">The type of object contained in the enumerable.</typeparam>
/// <param name="parameterName">Parameter name (embedded in exception if thrown).</param>
/// <param name="parameterValue">The value to validate.</param>
/// <param name="minimumCount">The minimum number of items required in the enumerable.</param>
/// <exception cref="ArgumentNullException">Enumerable object is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Enumerable does not contain at least <paramref name="minimumCount"/> number of objects.</exception>
public static void NotNullAndHasMinimumCount<T>(
string parameterName,
IEnumerable<T>? parameterValue,
int minimumCount)
{
NotEmptyOrWhitespace(nameof(parameterName), parameterName);

if (parameterValue == null)
{
throw new ArgumentNullException(parameterName);
}

if (parameterValue.Count() < minimumCount)
{
throw new ArgumentOutOfRangeException(
parameterName,
$"{parameterName} does not contain at least {minimumCount} elements.");
}
}
}
}
39 changes: 39 additions & 0 deletions src/Shared-lib/Helpers/EnvironmentHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

namespace Microsoft.CST.OpenSource.Helpers
{
using System;
using System.Reflection;

public class EnvironmentHelper
{
public static NLog.ILogger Logger { get; set; } = NLog.LogManager.GetCurrentClassLogger();

/// <summary>
/// Overrides static members starting with ENV_ with the respective environment variables.Allows
/// users to easily override fields like API endpoint roots. Only strings are supported.
/// </summary>
/// <param name="targetObject"> Examine this object (using reflection) </param>
public static void OverrideEnvironmentVariables(object targetObject)
{
foreach (FieldInfo fieldInfo in targetObject.GetType().GetFields(BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic))
{
if (fieldInfo.FieldType.FullName == "System.String" &&
fieldInfo.Name.StartsWith("ENV_") &&
fieldInfo.Name.Length > 4)
{
string? bareName = fieldInfo.Name[4..];

string? value = Environment.GetEnvironmentVariable(bareName);
if (value != null)
{
Logger.Debug("Assiging value of {0} to {1}", bareName, fieldInfo.Name);
fieldInfo.SetValue(null, value);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.

namespace Microsoft.CST.OpenSource.Shared.Extensions
namespace Microsoft.CST.OpenSource.Helpers
{
/// <summary>
/// A utilities class for string extension functions.
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Microsoft.CST.OpenSource.Model
{
using Microsoft.CST.OpenSource.Shared;
using Newtonsoft.Json;
using Octokit;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CSTRepository = Microsoft.CST.OpenSource.Model.Repository;
using Utilities;
using CSTRepository = Model.Repository;
using GHRepository = Octokit.Repository;

public class Repository
Expand All @@ -30,7 +30,7 @@ public class Repository
public bool? Archived { get; set; }

[JsonProperty(PropertyName = "contributors", NullValueHandling = NullValueHandling.Ignore)]
public List<User>? Contributors { get; set; }
public List<Model.User>? Contributors { get; set; }

[JsonProperty(PropertyName = "created_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? CreatedAt { get; set; }
Expand All @@ -54,19 +54,19 @@ public class Repository
public string? Language { get; set; }

[JsonProperty(PropertyName = "licenses", NullValueHandling = NullValueHandling.Ignore)]
public List<License>? Licenses { get; set; }
public List<Model.License>? Licenses { get; set; }

[JsonProperty(PropertyName = "linked_data", NullValueHandling = NullValueHandling.Ignore)]
public LinkedData? LinkedData { get; set; }

[JsonProperty(PropertyName = "maintainers", NullValueHandling = NullValueHandling.Ignore)]
public List<User>? Maintainers { get; set; }
public List<Model.User>? Maintainers { get; set; }

[JsonProperty(PropertyName = "openissues_count", NullValueHandling = NullValueHandling.Ignore)]
public int? OpenIssuesCount { get; set; }

[JsonProperty(PropertyName = "owner", NullValueHandling = NullValueHandling.Ignore)]
public User? Owner { get; set; }
public Model.User? Owner { get; set; }

[JsonProperty(PropertyName = "parent", NullValueHandling = NullValueHandling.Ignore)]
public string? Parent { get; set; }
Expand Down Expand Up @@ -121,33 +121,33 @@ public class Repository
Archived = ghRepository.Archived;
CreatedAt = ghRepository.CreatedAt;
UpdatedAt = ghRepository.UpdatedAt;
Description = Utilities.GetMaxClippedLength(ghRepository.Description);
Description = OssUtilities.GetMaxClippedLength(ghRepository.Description);
IsFork = ghRepository.Fork;
Forks = ghRepository.ForksCount;
Homepage = Utilities.GetMaxClippedLength(ghRepository.Homepage);
Homepage = OssUtilities.GetMaxClippedLength(ghRepository.Homepage);
Id = ghRepository.Id;
Language = Utilities.GetMaxClippedLength(ghRepository.Language);
Language = OssUtilities.GetMaxClippedLength(ghRepository.Language);
Name = ghRepository.Name;
OpenIssuesCount = ghRepository.OpenIssuesCount;
Parent = ghRepository.Parent?.Url;
PushedAt = ghRepository.PushedAt;
Size = ghRepository.Size;
FollowersCount = ghRepository.StargazersCount;
Uri = Utilities.GetMaxClippedLength(ghRepository.Url);
Uri = OssUtilities.GetMaxClippedLength(ghRepository.Url);
StakeholdersCount = ghRepository.WatchersCount;

if (ghRepository.License is not null)
{
Licenses ??= new List<License>();
Licenses.Add(new License()
Licenses ??= new List<Model.License>();
Licenses.Add(new Model.License()
{
Name = ghRepository.License.Name,
Url = ghRepository.License.Url,
SPIX_ID = ghRepository.License.SpdxId
});
}

Owner ??= new User()
Owner ??= new Model.User()
{
Id = ghRepository.Owner.Id,
Name = ghRepository.Owner.Name,
Expand Down
Loading

0 comments on commit a25747d

Please sign in to comment.