WIP NRT for deployment project

This commit is contained in:
Kevin Bost
2025-01-21 00:25:41 -08:00
parent fdeef605d4
commit e5cde62b45
10 changed files with 74 additions and 83 deletions

View File

@@ -9,15 +9,15 @@ namespace Velopack.Deployment;
public class AzureDownloadOptions : RepositoryOptions, IObjectDownloadOptions
{
public string Account { get; set; }
public string? Account { get; set; }
public string Key { get; set; }
public string? Key { get; set; }
public string Endpoint { get; set; }
public string? Endpoint { get; set; }
public string Container { get; set; }
public string? Container { get; set; }
public string SasToken { get; set; }
public string? SasToken { get; set; }
}
public class AzureUploadOptions : AzureDownloadOptions, IObjectUploadOptions
@@ -62,7 +62,7 @@ public class AzureRepository : ObjectRepository<AzureDownloadOptions, AzureUploa
"Deleting " + key);
}
protected override async Task<byte[]> GetObjectBytes(BlobContainerClient client, string key)
protected override async Task<byte[]?> GetObjectBytes(BlobContainerClient client, string key)
{
return await RetryAsyncRet(
async () => {

View File

@@ -1,4 +1,4 @@
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
using System.Collections.ObjectModel;
using System.Globalization;
@@ -12,7 +12,7 @@ namespace Velopack.Deployment;
public class GitHubHttpClient : IHttpClient
{
private HttpClient _client;
private HttpClient? _client;
public const string RedirectCountKey = "RedirectCount";
@@ -34,11 +34,11 @@ public class GitHubHttpClient : IHttpClient
public void Dispose()
{
_client.Dispose();
_client?.Dispose();
_client = null;
}
public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object> preprocessResponseBody = null)
public async Task<IResponse> Send(IRequest request, CancellationToken cancellationToken, Func<object, object>? preprocessResponseBody = null)
{
if (_client == null) {
throw new ObjectDisposedException(nameof(GitHubHttpClient));
@@ -49,25 +49,27 @@ public class GitHubHttpClient : IHttpClient
}
using (var requestMessage = BuildRequestMessage(request)) {
var responseMessage = await SendAsync(requestMessage).ConfigureAwait(false);
using var requestMessage = BuildRequestMessage(request);
var responseMessage = await SendAsync(requestMessage).ConfigureAwait(false);
return await BuildResponse(responseMessage, preprocessResponseBody).ConfigureAwait(false);
}
return await BuildResponse(responseMessage, preprocessResponseBody).ConfigureAwait(false);
}
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
private async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
// Clone the request/content in case we get a redirect
var clonedRequest = await CloneHttpRequestMessageAsync(request).ConfigureAwait(false);
// Send initial response
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None).ConfigureAwait(false);
var response = await _client!.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None).ConfigureAwait(false);
// Need to determine time on client computer as soon as possible.
var receivedTime = DateTimeOffset.Now;
IDictionary<string, object?> properties = request.Options;
// Since Properties are stored as objects, serialize to HTTP round-tripping string (Format: r)
// Resolution is limited to one-second, matching the resolution of the HTTP Date header
request.Properties[ReceivedTimeHeaderName] =
properties[ReceivedTimeHeaderName] =
receivedTime.ToString("r", CultureInfo.InvariantCulture);
// Can't redirect without somewhere to redirect to.
@@ -77,8 +79,8 @@ public class GitHubHttpClient : IHttpClient
// Don't redirect if we exceed max number of redirects
var redirectCount = 0;
if (request.Properties.Keys.Contains(RedirectCountKey)) {
redirectCount = (int) request.Properties[RedirectCountKey];
if (properties.TryGetValue(RedirectCountKey, out var redirectCountValue) && redirectCountValue is int value) {
redirectCount = value;
}
if (redirectCount > 3) {
@@ -97,13 +99,13 @@ public class GitHubHttpClient : IHttpClient
}
// Increment the redirect count
clonedRequest.Properties[RedirectCountKey] = ++redirectCount;
((IDictionary<string, object?>)clonedRequest.Options)[RedirectCountKey] = ++redirectCount;
// Set the new Uri based on location header
clonedRequest.RequestUri = response.Headers.Location;
// Clear authentication if redirected to a different host
if (string.Compare(clonedRequest.RequestUri.Host, request.RequestUri.Host, StringComparison.OrdinalIgnoreCase) != 0) {
if (string.Compare(clonedRequest.RequestUri.Host, request.RequestUri!.Host, StringComparison.OrdinalIgnoreCase) != 0) {
clonedRequest.Headers.Authorization = null;
}
@@ -116,11 +118,9 @@ public class GitHubHttpClient : IHttpClient
protected virtual HttpRequestMessage BuildRequestMessage(IRequest request)
{
if (request == null) {
throw new ArgumentNullException(nameof(request));
}
HttpRequestMessage requestMessage = null;
ArgumentNullException.ThrowIfNull(request);
HttpRequestMessage? requestMessage = null;
try {
var fullUri = new Uri(request.BaseAddress, request.Endpoint);
requestMessage = new HttpRequestMessage(request.Method, fullUri);
@@ -145,9 +145,7 @@ public class GitHubHttpClient : IHttpClient
requestMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(request.ContentType);
}
} catch (Exception) {
if (requestMessage != null) {
requestMessage.Dispose();
}
requestMessage?.Dispose();
throw;
}
@@ -155,7 +153,7 @@ public class GitHubHttpClient : IHttpClient
return requestMessage;
}
static string GetContentMediaType(HttpContent httpContent)
private static string? GetContentMediaType(HttpContent httpContent)
{
if (httpContent.Headers?.ContentType != null) {
return httpContent.Headers.ContentType.MediaType;
@@ -170,14 +168,12 @@ public class GitHubHttpClient : IHttpClient
return null;
}
protected virtual async Task<IResponse> BuildResponse(HttpResponseMessage responseMessage, Func<object, object> preprocessResponseBody)
protected virtual async Task<IResponse> BuildResponse(HttpResponseMessage responseMessage, Func<object, object>? preprocessResponseBody)
{
if (responseMessage == null) {
throw new ArgumentNullException(nameof(responseMessage));
}
ArgumentNullException.ThrowIfNull(responseMessage);
object responseBody = null;
string contentType = null;
object? responseBody = null;
string? contentType = null;
// We added support for downloading images,zip-files and application/octet-stream.
// Let's constrain this appropriately.
@@ -209,7 +205,7 @@ public class GitHubHttpClient : IHttpClient
// Add Client response received time as a synthetic header
const string receivedTimeHeaderName = ReceivedTimeHeaderName;
if (responseMessage.RequestMessage?.Properties is IDictionary<string, object> reqProperties
&& reqProperties.TryGetValue(receivedTimeHeaderName, out object receivedTimeObj)
&& reqProperties.TryGetValue(receivedTimeHeaderName, out object? receivedTimeObj)
&& receivedTimeObj is string receivedTimeString
&& !responseHeaders.ContainsKey(receivedTimeHeaderName)) {
responseHeaders[receivedTimeHeaderName] = receivedTimeString;
@@ -261,12 +257,10 @@ public class GitHubHttpClient : IHttpClient
private class GitHubResponse : IResponse
{
public GitHubResponse(HttpStatusCode statusCode, object body, IDictionary<string, string> headers, string contentType)
public GitHubResponse(HttpStatusCode statusCode, object? body, IDictionary<string, string> headers, string? contentType)
{
if (headers == null) {
throw new ArgumentNullException(nameof(headers));
}
ArgumentNullException.ThrowIfNull(headers);
StatusCode = statusCode;
Body = body;
Headers = new ReadOnlyDictionary<string, string>(headers);
@@ -275,7 +269,7 @@ public class GitHubHttpClient : IHttpClient
}
/// <inheritdoc />
public object Body { get; private set; }
public object? Body { get; private set; }
/// <summary>
/// Information about the API.
@@ -295,7 +289,7 @@ public class GitHubHttpClient : IHttpClient
/// <summary>
/// The content type of the response.
/// </summary>
public string ContentType { get; private set; }
public string? ContentType { get; private set; }
}
private static class ApiInfoParser
@@ -321,14 +315,12 @@ public class GitHubHttpClient : IHttpClient
public static ApiInfo ParseResponseHeaders(IDictionary<string, string> responseHeaders)
{
if (responseHeaders == null) {
throw new ArgumentNullException(nameof(responseHeaders));
}
ArgumentNullException.ThrowIfNull(responseHeaders);
var httpLinks = new Dictionary<string, Uri>();
var oauthScopes = new List<string>();
var acceptedOauthScopes = new List<string>();
string etag = null;
string? etag = null;
var acceptedOauthScopesKey = LookupHeader(responseHeaders, "X-Accepted-OAuth-Scopes");
if (Exists(acceptedOauthScopesKey)) {

View File

@@ -4,7 +4,6 @@ using Octokit;
using Velopack.Core;
using Velopack.NuGet;
using Velopack.Packaging;
using Velopack.Packaging.Exceptions;
using Velopack.Sources;
using Velopack.Util;
@@ -14,20 +13,20 @@ public class GitHubDownloadOptions : RepositoryOptions
{
public bool Prerelease { get; set; }
public string RepoUrl { get; set; }
public string? RepoUrl { get; set; }
public string Token { get; set; }
public string? Token { get; set; }
}
public class GitHubUploadOptions : GitHubDownloadOptions
{
public bool Publish { get; set; }
public string ReleaseName { get; set; }
public string? ReleaseName { get; set; }
public string TagName { get; set; }
public string? TagName { get; set; }
public string TargetCommitish { get; set; }
public string? TargetCommitish { get; set; }
public bool Merge { get; set; }
}

View File

@@ -16,9 +16,9 @@ public class GiteaDownloadOptions : RepositoryOptions
{
public bool Prerelease { get; set; }
public string RepoUrl { get; set; }
public string? RepoUrl { get; set; }
public string Token { get; set; }
public string? Token { get; set; }
///// <summary>
///// Example https://gitea.com
@@ -32,11 +32,11 @@ public class GiteaUploadOptions : GiteaDownloadOptions
{
public bool Publish { get; set; }
public string ReleaseName { get; set; }
public string? ReleaseName { get; set; }
public string TagName { get; set; }
public string? TagName { get; set; }
public string TargetCommitish { get; set; }
public string? TargetCommitish { get; set; }
public bool Merge { get; set; }
}
@@ -91,7 +91,7 @@ public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSourc
var apiInstance = new RepositoryApi(config);
// Get all releases
// Get repository info for total releases
List<Release> existingReleases = null;
List<Release>? existingReleases = null;
ApiResponse<Repository> repositoryInfo = await apiInstance.RepoGetWithHttpInfoAsync(repoOwner, repoName);
if (repositoryInfo != null && repositoryInfo.StatusCode == HttpStatusCode.OK) {
// Get all releases
@@ -182,7 +182,7 @@ public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSourc
}
}
private async Task UploadFileAsAsset(RepositoryApi client, Release release, string repoOwner, string repoName, string filePath)
private static async Task UploadFileAsAsset(RepositoryApi client, Release release, string repoOwner, string repoName, string filePath)
{
using var stream = File.OpenRead(filePath);
// Create a release attachment

View File

@@ -5,7 +5,7 @@ namespace Velopack.Deployment;
public class HttpDownloadOptions : RepositoryOptions
{
public string Url { get; set; }
public string? Url { get; set; }
}
public class HttpRepository : SourceRepository<HttpDownloadOptions, SimpleWebSource>

View File

@@ -7,7 +7,7 @@ namespace Velopack.Deployment;
public class LocalDownloadOptions : RepositoryOptions, IObjectDownloadOptions
{
public DirectoryInfo TargetPath { get; set; }
public DirectoryInfo? TargetPath { get; set; }
}
public class LocalUploadOptions : LocalDownloadOptions, IObjectUploadOptions
@@ -32,11 +32,11 @@ public class LocalRepository(ILogger logger) : ObjectRepository<LocalDownloadOpt
return Task.CompletedTask;
}
protected override Task<byte[]> GetObjectBytes(DirectoryInfo client, string key)
protected override async Task<byte[]?> GetObjectBytes(DirectoryInfo client, string key)
{
var target = Path.Combine(client.FullName, key);
Log.Info("Reading: " + target);
return File.ReadAllBytesAsync(target);
return await File.ReadAllBytesAsync(target);
}
protected override Task UploadObject(DirectoryInfo client, string key, FileInfo f, bool overwriteRemote, bool noCache)

View File

@@ -8,19 +8,19 @@ namespace Velopack.Deployment;
public class S3DownloadOptions : RepositoryOptions, IObjectDownloadOptions
{
public string KeyId { get; set; }
public string? KeyId { get; set; }
public string Secret { get; set; }
public string? Secret { get; set; }
public string Session { get; set; }
public string? Session { get; set; }
public string Region { get; set; }
public string? Region { get; set; }
public string Endpoint { get; set; }
public string? Endpoint { get; set; }
public string Bucket { get; set; }
public string? Bucket { get; set; }
public string Prefix { get; set; }
public string? Prefix { get; set; }
}
public class S3UploadOptions : S3DownloadOptions, IObjectUploadOptions
@@ -129,7 +129,7 @@ public class S3Repository : ObjectRepository<S3DownloadOptions, S3UploadOptions,
prefix = "";
}
if (!string.IsNullOrEmpty(prefix) && !prefix.EndsWith("/")) {
if (!string.IsNullOrEmpty(prefix) && !prefix.EndsWith('/')) {
prefix += "/";
}
@@ -141,7 +141,7 @@ public class S3Repository : ObjectRepository<S3DownloadOptions, S3UploadOptions,
await RetryAsync(() => client.DeleteObjectAsync(key), "Deleting " + key);
}
protected override async Task<byte[]> GetObjectBytes(S3BucketClient client, string key)
protected override async Task<byte[]?> GetObjectBytes(S3BucketClient client, string key)
{
return await RetryAsyncRet(
async () => {
@@ -165,16 +165,15 @@ public class S3Repository : ObjectRepository<S3DownloadOptions, S3UploadOptions,
var client = CreateClient(options);
await RetryAsync(
async () => {
using (var obj = await client.GetObjectAsync(entry.FileName)) {
await obj.WriteResponseStreamToFileAsync(filePath, false, CancellationToken.None);
}
using var obj = await client.GetObjectAsync(entry.FileName);
await obj.WriteResponseStreamToFileAsync(filePath, false, CancellationToken.None);
},
$"Downloading {entry.FileName}...");
}
protected override async Task UploadObject(S3BucketClient client, string key, FileInfo f, bool overwriteRemote, bool noCache)
{
string deleteOldVersionId = null;
string? deleteOldVersionId = null;
// try to detect an existing remote file of the same name
try {

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);CA2007;CS8002</NoWarn>
</PropertyGroup>

View File

@@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using Microsoft.Extensions.Logging;
using Velopack.Core;
using Velopack.Packaging;
@@ -25,7 +25,7 @@ public abstract class ObjectRepository<TDown, TUp, TClient> : DownRepository<TDo
protected abstract Task UploadObject(TClient client, string key, FileInfo f, bool overwriteRemote, bool noCache);
protected abstract Task DeleteObject(TClient client, string key);
protected abstract Task<byte[]> GetObjectBytes(TClient client, string key);
protected abstract Task<byte[]?> GetObjectBytes(TClient client, string key);
protected abstract TClient CreateClient(TDown options);
protected byte[] GetFileMD5Checksum(string filePath)
@@ -63,7 +63,7 @@ public abstract class ObjectRepository<TDown, TUp, TClient> : DownRepository<TDo
Log.Info($"{releaseEntries.Length} merged local/remote release(s).");
var toDelete = new VelopackAsset[0];
var toDelete = Array.Empty<VelopackAsset>();
if (options.KeepMaxReleases > 0) {
var fullReleases = releaseEntries

View File

@@ -8,7 +8,7 @@ namespace Velopack.Deployment;
public class RepositoryOptions : IOutputOptions
{
private string _channel;
private string? _channel;
public RuntimeOs TargetOs { get; set; }
@@ -17,7 +17,7 @@ public class RepositoryOptions : IOutputOptions
set => _channel = value;
}
public DirectoryInfo ReleaseDir { get; set; }
public DirectoryInfo? ReleaseDir { get; set; }
public double Timeout { get; set; } = 30d;
}