Skip to content

Use .NET Host DI container #6284

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

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 32 additions & 0 deletions src/kiota/CliOverrides.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Kiota.Builder.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using static kiota.ServiceConstants;

namespace kiota;

internal class CliOverrides(
IOptionsMonitor<KiotaConfiguration> kiotaConfiguration
)
{
public bool? DisableSslValidation
{
get;
set;
}

public string? OutputPath
{
get;
set;
}

internal bool GetEffectiveDisableSslValidation()
{
return this.DisableSslValidation ?? kiotaConfiguration.CurrentValue.Generation.DisableSSLValidation;
}
internal string GetEffectiveOutputPath()
{
return this.OutputPath ?? kiotaConfiguration.CurrentValue.Generation.OutputPath;
}
}
50 changes: 49 additions & 1 deletion src/kiota/Extension/KiotaHostExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Azure.Monitor.OpenTelemetry.Exporter;
using System.CommandLine.Hosting;
using Azure.Monitor.OpenTelemetry.Exporter;
using kiota.Telemetry;
using kiota.Telemetry.Config;
using Kiota.Builder.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
Expand All @@ -13,6 +16,51 @@

internal static class KiotaHostExtensions
{
internal static IHostBuilder ConfigureBaseServices(this IHostBuilder builder)
{
builder.ConfigureAppConfiguration(static (ctx, configuration) =>
{
var defaultStream = new MemoryStream(Kiota.Generated.KiotaAppSettings.Default());
configuration.AddJsonStream(defaultStream)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables(prefix: "KIOTA_");
});
builder.ConfigureServices(static (ctx, services) =>
{
services.Configure<KiotaConfiguration>(ctx.Configuration);
services.Configure<TelemetryConfig>(ctx.Configuration.GetSection(TelemetryConfig.ConfigSectionKey));
services.AddHttpClient(string.Empty).ConfigurePrimaryHttpMessageHandler(static sp =>
{
var overrides = sp.GetRequiredService<CliOverrides>();
var httpClientHandler = new HttpClientHandler();
if (overrides.GetEffectiveDisableSslValidation())
{
httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

Check warning on line 38 in src/kiota/Extension/KiotaHostExtensions.cs

View workflow job for this annotation

GitHub Actions / Build

Enable server certificate validation on this SSL/TLS connection (https://rules.sonarsource.com/csharp/RSPEC-4830)

Check failure

Code scanning / SonarCloud

Server certificates should be verified during SSL/TLS connections High

Enable server certificate validation on this SSL/TLS connection See more on SonarQube Cloud
}
return httpClientHandler;
});
services.AddSingleton<CliOverrides>();
services.AddKeyedSingleton<GenerationConfiguration>(ServiceConstants.ServiceKeys.Default);
});
builder.ConfigureLogging(static (ctx, logging) =>
{
logging.ClearProviders();
#if DEBUG
logging.AddDebug();
#endif
logging.AddEventSourceLogger();

// TODO: Add the file logger and find a strategy for changing the output path

Check warning on line 53 in src/kiota/Extension/KiotaHostExtensions.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
var parseResult = ctx.GetInvocationContext().ParseResult;
var logLevelResult = parseResult.FindResultFor(KiotaHost.LogLevelOption.Value);
if (logLevelResult != null)
{
logging.SetMinimumLevel(logLevelResult.GetValueOrDefault<LogLevel>());
}
});
return builder;
}

internal static IHostBuilder ConfigureKiotaTelemetryServices(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(ConfigureServiceContainer);
Expand Down
82 changes: 28 additions & 54 deletions src/kiota/Handlers/BaseKiotaCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,24 @@
generationConfiguration.Serializers = defaultGenerationConfiguration.Serializers;
generationConfiguration.Deserializers = defaultGenerationConfiguration.Deserializers;
}
protected TempFolderCachingAccessTokenProvider GetGitHubDeviceStorageService(ILogger logger) => new()
protected TempFolderCachingAccessTokenProvider GetGitHubDeviceStorageService(KiotaConfiguration configuration, ILogger logger) => new()
{
Logger = logger,
ApiBaseUrl = Configuration.Search.GitHub.ApiBaseUrl,
ApiBaseUrl = configuration.Search.GitHub.ApiBaseUrl,
Concrete = null,
AppId = Configuration.Search.GitHub.AppId,
AppId = configuration.Search.GitHub.AppId,
};
protected static TempFolderTokenStorageService GetGitHubPatStorageService(ILogger logger) => new()
{
Logger = logger,
FileName = "pat-api.github.com"
};

private HttpClient? _httpClient;
protected HttpClient httpClient
{
get
{
_httpClient ??= GetHttpClient();
return _httpClient;
}
}
public required Option<LogLevel> LogLevelOption
{
get; init;
}
protected KiotaConfiguration Configuration
{
get => ConfigurationFactory.Value;
}
private readonly Lazy<KiotaConfiguration> ConfigurationFactory = new(() =>

Check warning on line 39 in src/kiota/Handlers/BaseKiotaCommandHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Remove the unused private field 'ConfigurationFactory'. (https://rules.sonarsource.com/csharp/RSPEC-1144)
{
var builder = new ConfigurationBuilder();
using var defaultStream = new MemoryStream(Kiota.Generated.KiotaAppSettings.Default());
Expand All @@ -62,71 +49,58 @@
return configObject;
});

protected HttpClient GetHttpClient()
{
var httpClientHandler = new HttpClientHandler();
if (Configuration.Generation.DisableSSLValidation)
httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

var httpClient = new HttpClient(httpClientHandler);

disposables.Add(httpClientHandler);
disposables.Add(httpClient);

return httpClient;
}

private const string GitHubScope = "repo";
private Func<CancellationToken, Task<bool>> GetIsGitHubDeviceSignedInCallback(ILogger logger) => (cancellationToken) =>
private Func<CancellationToken, Task<bool>> GetIsGitHubDeviceSignedInCallback(KiotaConfiguration configuration, ILogger logger) => (cancellationToken) =>
{
var provider = GetGitHubDeviceStorageService(logger);
var provider = GetGitHubDeviceStorageService(configuration, logger);
return provider.TokenStorageService.Value.IsTokenPresentAsync(cancellationToken);
};
private static Func<CancellationToken, Task<bool>> GetIsGitHubPatSignedInCallback(ILogger logger) => (cancellationToken) =>
{
var provider = GetGitHubPatStorageService(logger);
return provider.IsTokenPresentAsync(cancellationToken);
};
private IAuthenticationProvider GetGitHubAuthenticationProvider(ILogger logger) =>
new DeviceCodeAuthenticationProvider(Configuration.Search.GitHub.AppId,
private IAuthenticationProvider GetGitHubAuthenticationProvider(string appId, string host, ILogger logger, HttpClient httpClient) =>
new DeviceCodeAuthenticationProvider(appId,
GitHubScope,
new List<string> { Configuration.Search.GitHub.ApiBaseUrl.Host },
new List<string> { host },
httpClient,
DisplayGitHubDeviceCodeLoginMessage,
logger);
private IAuthenticationProvider GetGitHubPatAuthenticationProvider(ILogger logger) =>
new PatAuthenticationProvider(Configuration.Search.GitHub.AppId,
private IAuthenticationProvider GetGitHubPatAuthenticationProvider(string appId, string host, ILogger logger) =>
new PatAuthenticationProvider(appId,
GitHubScope,
new List<string> { Configuration.Search.GitHub.ApiBaseUrl.Host },
new List<string> { host },
logger,
GetGitHubPatStorageService(logger));
protected async Task<KiotaSearcher> GetKiotaSearcherAsync(ILoggerFactory loggerFactory, CancellationToken cancellationToken)
protected async Task<KiotaSearcher> GetKiotaSearcherAsync(KiotaConfiguration configuration, ILoggerFactory loggerFactory, HttpClient httpClient, CancellationToken cancellationToken)
{
var logger = loggerFactory.CreateLogger<KiotaSearcher>();
var deviceCodeSignInCallback = GetIsGitHubDeviceSignedInCallback(logger);
var deviceCodeSignInCallback = GetIsGitHubDeviceSignedInCallback(configuration, logger);
var patSignInCallBack = GetIsGitHubPatSignedInCallback(logger);
var isDeviceCodeSignedIn = await deviceCodeSignInCallback(cancellationToken).ConfigureAwait(false);
var isPatSignedIn = await patSignInCallBack(cancellationToken).ConfigureAwait(false);
var (provider, callback) = (isDeviceCodeSignedIn, isPatSignedIn) switch
{
(true, _) => ((IAuthenticationProvider?)GetGitHubAuthenticationProvider(logger), deviceCodeSignInCallback),
(_, true) => (GetGitHubPatAuthenticationProvider(logger), patSignInCallBack),
(true, _) => ((IAuthenticationProvider?)GetGitHubAuthenticationProvider(configuration.Search.GitHub.AppId, configuration.Search.GitHub.ApiBaseUrl.Host, logger, httpClient), deviceCodeSignInCallback),
(_, true) => (GetGitHubPatAuthenticationProvider(configuration.Search.GitHub.AppId, configuration.Search.GitHub.ApiBaseUrl.Host, logger), patSignInCallBack),
(_, _) => (null, (CancellationToken cts) => Task.FromResult(false))
};
return new KiotaSearcher(logger, Configuration.Search, httpClient, provider, callback);
return new KiotaSearcher(logger, configuration.Search, httpClient, provider, callback);
}
public int Invoke(InvocationContext context)
{
throw new InvalidOperationException("This command handler is async only");
}
protected async Task CheckForNewVersionAsync(ILogger logger, CancellationToken cancellationToken)
protected async Task CheckForNewVersionAsync(KiotaConfiguration configuration, HttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
{
if (Configuration.Update.Disabled)
if (configuration.Update.Disabled)
{
return;
}

var updateService = new UpdateService(httpClient, logger, Configuration.Update);
// TODO: register service in DI container.

Check warning on line 102 in src/kiota/Handlers/BaseKiotaCommandHandler.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
var updateService = new UpdateService(httpClient, logger, configuration.Update);
var result = await updateService.GetUpdateMessageAsync(Kiota.Generated.KiotaVersion.Current(), cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(result))
DisplayWarning(result);
Expand Down Expand Up @@ -158,10 +132,10 @@
return string.Empty;
return Path.IsPathRooted(source) || source.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? source : NormalizeSlashesInPath(Path.Combine(Directory.GetCurrentDirectory(), source));
}
protected void AssignIfNotNullOrEmpty(string? input, Action<GenerationConfiguration, string> assignment)
protected void AssignIfNotNullOrEmpty(KiotaConfiguration configuration, string? input, Action<GenerationConfiguration, string> assignment)
{
if (!string.IsNullOrEmpty(input))
assignment.Invoke(Configuration.Generation, input);
assignment.Invoke(configuration.Generation, input);
}
protected static string NormalizeSlashesInPath(string path)
{
Expand Down Expand Up @@ -362,18 +336,18 @@
DisplayHint("Hint: use the logout command to sign out of GitHub.",
"Example: kiota logout github");
}
protected void DisplayManageInstallationHint()
protected void DisplayManageInstallationHint(KiotaConfiguration configuration)
{
DisplayHint($"Hint: go to {Configuration.Search.GitHub.AppManagement} to manage your which organizations and repositories Kiota has access to.");
DisplayHint($"Hint: go to {configuration.Search.GitHub.AppManagement} to manage your which organizations and repositories Kiota has access to.");
}
protected void DisplaySearchBasicHint()
{
DisplayHint("Hint: use the search command to search for an OpenAPI description.",
"Example: kiota search <search term>");
}
protected async Task DisplayLoginHintAsync(ILogger logger, CancellationToken token)
protected async Task DisplayLoginHintAsync(KiotaConfiguration configuration, ILogger logger, CancellationToken token)
{
var deviceCodeAuthProvider = GetGitHubDeviceStorageService(logger);
var deviceCodeAuthProvider = GetGitHubDeviceStorageService(configuration, logger);
var patStorage = GetGitHubPatStorageService(logger);
if (!await deviceCodeAuthProvider.TokenStorageService.Value.IsTokenPresentAsync(token) && !await patStorage.IsTokenPresentAsync(token))
{
Expand All @@ -386,9 +360,9 @@
if (KiotaHost.IsConfigPreviewEnabled.Value)
DisplayWarning("Warning: the kiota generate and update commands are deprecated, use kiota client commands instead.");
}
protected void WarnUsingPreviewLanguage(GenerationLanguage language)
protected void WarnUsingPreviewLanguage(KiotaConfiguration configuration, GenerationLanguage language)
{
if (Configuration.Languages.TryGetValue(language.ToString(), out var languageInformation) && languageInformation.MaturityLevel is not LanguageMaturityLevel.Stable)
if (configuration.Languages.TryGetValue(language.ToString(), out var languageInformation) && languageInformation.MaturityLevel is not LanguageMaturityLevel.Stable)
DisplayWarning($"Warning: the {language} language is in preview ({languageInformation.MaturityLevel}) some features are not fully supported and source breaking changes will happen with future updates.");
}
protected void DisplayGitHubDeviceCodeLoginMessage(Uri uri, string code)
Expand Down
Loading
Loading