tools/code/publisher/Common.cs (151 lines of code) (raw):
using Azure.Core;
using common;
using LanguageExt;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Frozen;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace publisher;
/// <summary>
/// Get files that changed in the commit. If no commit ID is provided, it will return all artifact files.
/// </summary>
/// <returns></returns>
public delegate FrozenSet<FileInfo> GetPublisherFiles();
/// <summary>
/// Lists all files in the artifact directory. If a commit ID is provided, it will list
/// all files as of that commit.
/// </summary>
public delegate FrozenSet<FileInfo> GetArtifactFiles();
/// <summary>
/// Returns a dictionary of files in the previous commit. The key is the file,
/// and the value is a function that returns its contents. If the dictionary contained the actual
/// contents, it could get prohibitively large.
/// </summary>
/// <returns></returns>
public delegate FrozenDictionary<FileInfo, Func<CancellationToken, ValueTask<Option<BinaryData>>>> GetArtifactsInPreviousCommit();
/// <summary>
/// Gets the contents of a file. If the publisher is running in the context of a Git commit,
/// the file contents will be retrieved from the Git repository as of that commit ID.
/// Otherwise, the file contents will come from the local file system.
/// If the file does not exist, returns <see cref="Option"/>.None.
/// </summary>
public delegate ValueTask<Option<BinaryData>> TryGetFileContents(FileInfo fileInfo, CancellationToken cancellationToken);
/// <summary>
/// An action to be performed by the publisher. Shortcut for a function
/// that takes a <see cref="CancellationToken"/> and returns a <see cref="ValueTask"/>.
/// </summary>
public delegate ValueTask PublisherAction(CancellationToken cancellationToken);
public delegate Option<CommitId> TryGetCommitId();
public delegate ValueTask<Option<BinaryData>> TryGetFileContentsInCommit(FileInfo fileInfo, CommitId commitId, CancellationToken cancellationToken);
internal static class CommonModule
{
public static void ConfigureGetPublisherFiles(IHostApplicationBuilder builder)
{
ConfigureTryGetCommitId(builder);
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.AddMemoryCache();
builder.Services.TryAddSingleton(GetGetPublisherFiles);
}
private static GetPublisherFiles GetGetPublisherFiles(IServiceProvider provider)
{
var tryGetCommitId = provider.GetRequiredService<TryGetCommitId>();
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
var cache = provider.GetRequiredService<IMemoryCache>();
var cacheKey = Guid.NewGuid().ToString();
return () =>
cache.GetOrCreate(cacheKey, _ => getFiles())!;
FrozenSet<FileInfo> getFiles() =>
tryGetCommitId()
.Map(getFilesFromCommitId)
.IfNone(serviceDirectory.GetFilesRecursively);
FrozenSet<FileInfo> getFilesFromCommitId(CommitId commitId) =>
Git.GetChangedFilesInCommit(serviceDirectory.ToDirectoryInfo(), commitId);
}
public static void ConfigureGetArtifactFiles(IHostApplicationBuilder builder)
{
ConfigureTryGetCommitId(builder);
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.AddMemoryCache();
builder.Services.TryAddSingleton(GetGetArtifactFiles);
}
private static GetArtifactFiles GetGetArtifactFiles(IServiceProvider provider)
{
var tryGetCommitId = provider.GetRequiredService<TryGetCommitId>();
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
var cache = provider.GetRequiredService<IMemoryCache>();
var cacheKey = Guid.NewGuid().ToString();
return () =>
cache.GetOrCreate(cacheKey, _ => getFiles())!;
FrozenSet<FileInfo> getFiles() =>
tryGetCommitId()
.Map(getFilesFromCommitId)
.IfNone(serviceDirectory.GetFilesRecursively);
FrozenSet<FileInfo> getFilesFromCommitId(CommitId commitId) =>
Git.GetExistingFilesInCommit(serviceDirectory.ToDirectoryInfo(), commitId);
}
public static void ConfigureGetArtifactsInPreviousCommit(IHostApplicationBuilder builder)
{
ConfigureTryGetCommitId(builder);
ConfigureTryGetFileContentsInCommit(builder);
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.AddMemoryCache();
builder.Services.TryAddSingleton(GetGetArtifactsInPreviousCommit);
}
private static GetArtifactsInPreviousCommit GetGetArtifactsInPreviousCommit(IServiceProvider provider)
{
var tryGetCommitId = provider.GetRequiredService<TryGetCommitId>();
var tryGetCommitContents = provider.GetRequiredService<TryGetFileContentsInCommit>();
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
var cache = provider.GetRequiredService<IMemoryCache>();
var cacheKey = Guid.NewGuid().ToString();
return () =>
cache.GetOrCreate(cacheKey, _ => getArtifacts())!;
FrozenDictionary<FileInfo, Func<CancellationToken, ValueTask<Option<BinaryData>>>> getArtifacts() =>
tryGetCommitId()
.Bind(tryGetPreviousCommitArtifacts)
.IfNone(() => FrozenDictionary<FileInfo, Func<CancellationToken, ValueTask<Option<BinaryData>>>>.Empty);
Option<FrozenDictionary<FileInfo, Func<CancellationToken, ValueTask<Option<BinaryData>>>>> tryGetPreviousCommitArtifacts(CommitId commitId) =>
Git.TryGetPreviousCommitId(serviceDirectory.ToDirectoryInfo(), commitId)
.Map(previousCommitId => Git.GetExistingFilesInCommit(serviceDirectory.ToDirectoryInfo(), previousCommitId)
.ToFrozenDictionary<FileInfo, FileInfo, Func<CancellationToken, ValueTask<Option<BinaryData>>>>
(keySelector: file => file,
elementSelector: file => (CancellationToken cancellationToken) => tryGetCommitContents(file, previousCommitId, cancellationToken)));
}
public static void ConfigureTryGetFileContents(IHostApplicationBuilder builder)
{
ConfigureTryGetCommitId(builder);
ConfigureTryGetFileContentsInCommit(builder);
builder.Services.TryAddSingleton(GetTryGetFileContents);
}
private static TryGetFileContents GetTryGetFileContents(IServiceProvider provider)
{
var tryGetCommitId = provider.GetRequiredService<TryGetCommitId>();
var tryGetCommitContents = provider.GetRequiredService<TryGetFileContentsInCommit>();
return async (file, cancellationToken) =>
await tryGetCommitId()
.BindTask(async commitId => await tryGetCommitContents(file, commitId, cancellationToken))
.Or(async () => await tryGetFileSystemContents(file, cancellationToken));
static async ValueTask<Option<BinaryData>> tryGetFileSystemContents(FileInfo file, CancellationToken cancellationToken) =>
file.Exists()
? await file.ReadAsBinaryData(cancellationToken)
: Option<BinaryData>.None;
}
private static void ConfigureTryGetFileContentsInCommit(IHostApplicationBuilder builder)
{
AzureModule.ConfigureManagementServiceDirectory(builder);
builder.Services.TryAddSingleton(GetTryGetFileContentsInCommit);
}
private static TryGetFileContentsInCommit GetTryGetFileContentsInCommit(IServiceProvider provider)
{
var serviceDirectory = provider.GetRequiredService<ManagementServiceDirectory>();
return async (file, commitId, cancellationToken) =>
await Git.TryGetFileContentsInCommit(serviceDirectory.ToDirectoryInfo(), file, commitId)
.MapTask(async stream =>
{
using (stream)
{
return await BinaryData.FromStreamAsync(stream, cancellationToken);
}
});
}
private static void ConfigureTryGetCommitId(IHostApplicationBuilder builder)
{
builder.Services.TryAddSingleton(GetTryGetCommitId);
}
private static TryGetCommitId GetTryGetCommitId(IServiceProvider provider)
{
var configuration = provider.GetRequiredService<IConfiguration>();
return () =>
configuration.TryGetValue("COMMIT_ID")
.Map(commitId => new CommitId(commitId));
}
}
file static class Common
{
public static FrozenSet<FileInfo> GetFilesRecursively(this ManagementServiceDirectory serviceDirectory) =>
serviceDirectory.ToDirectoryInfo()
.EnumerateFiles("*", SearchOption.AllDirectories)
.ToFrozenSet(x => x.FullName);
}