mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Adding flow API command
This provide a hidden Velopack Flow API command. This allows for easier scripting directly against the API as it leveraging the built in auth.
This commit is contained in:
@@ -34,7 +34,7 @@ public class PublishTask : MSBuildAsyncTask
|
||||
AllowInteractiveLogin = false,
|
||||
VelopackBaseUrl = ServiceUrl,
|
||||
ApiKey = ApiKey
|
||||
}, cancellationToken).ConfigureAwait(false)) {
|
||||
}, false, cancellationToken).ConfigureAwait(false)) {
|
||||
Logger.LogWarning("Not logged into Velopack Flow service, skipping publish. Please run vpk login.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using NuGet.Versioning;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using Markdig.Helpers;
|
||||
|
||||
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
@@ -17,11 +19,15 @@ namespace Velopack.Packaging.Flow;
|
||||
|
||||
public interface IVelopackFlowServiceClient
|
||||
{
|
||||
Task<bool> LoginAsync(VelopackLoginOptions? options, CancellationToken cancellationToken);
|
||||
Task<bool> LoginAsync(VelopackLoginOptions? options, bool suppressOutput, CancellationToken cancellationToken);
|
||||
Task LogoutAsync(VelopackServiceOptions? options, CancellationToken cancellationToken);
|
||||
|
||||
Task<Profile?> GetProfileAsync(VelopackServiceOptions? options, CancellationToken cancellationToken);
|
||||
|
||||
Task<string> InvokeEndpointAsync(VelopackServiceOptions? options, string endpointUri,
|
||||
string method,
|
||||
string? body,
|
||||
CancellationToken cancellationToken);
|
||||
Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl, RuntimeOs os, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -33,10 +39,12 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
|
||||
private AuthConfiguration? AuthConfiguration { get; set; }
|
||||
|
||||
public async Task<bool> LoginAsync(VelopackLoginOptions? options, CancellationToken cancellationToken)
|
||||
public async Task<bool> LoginAsync(VelopackLoginOptions? options, bool suppressOutput, CancellationToken cancellationToken)
|
||||
{
|
||||
options ??= new VelopackLoginOptions();
|
||||
Logger.LogInformation("Preparing to login to Velopack ({ServiceUrl})", options.VelopackBaseUrl);
|
||||
if (!suppressOutput) {
|
||||
Logger.LogInformation("Preparing to login to Velopack ({ServiceUrl})", options.VelopackBaseUrl);
|
||||
}
|
||||
|
||||
var authConfiguration = await GetAuthConfigurationAsync(options, cancellationToken);
|
||||
|
||||
@@ -45,7 +53,9 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
if (!string.IsNullOrWhiteSpace(options.ApiKey)) {
|
||||
HttpClient.DefaultRequestHeaders.Authorization = new(HmacHelper.HmacScheme, options.ApiKey);
|
||||
var profile = await GetProfileAsync(options, cancellationToken);
|
||||
Logger.LogInformation("{UserName} logged into Velopack with API key", profile?.GetDisplayName());
|
||||
if (!suppressOutput) {
|
||||
Logger.LogInformation("{UserName} logged into Velopack with API key", profile?.GetDisplayName());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
AuthenticationResult? rv = null;
|
||||
@@ -63,7 +73,9 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
HttpClient.DefaultRequestHeaders.Authorization = new("Bearer", rv.IdToken ?? rv.AccessToken);
|
||||
var profile = await GetProfileAsync(options, cancellationToken);
|
||||
|
||||
Logger.LogInformation("{UserName} logged into Velopack", profile?.GetDisplayName());
|
||||
if (!suppressOutput) {
|
||||
Logger.LogInformation("{UserName} logged into Velopack", profile?.GetDisplayName());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Logger.LogError("Failed to login to Velopack");
|
||||
@@ -94,6 +106,35 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
return await HttpClient.GetFromJsonAsync<Profile>(endpoint, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string> InvokeEndpointAsync(
|
||||
VelopackServiceOptions? options,
|
||||
string endpointUri,
|
||||
string method,
|
||||
string? body,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
AssertAuthenticated();
|
||||
var endpoint = GetEndpoint(endpointUri, options?.VelopackBaseUrl);
|
||||
|
||||
HttpRequestMessage request = new(new HttpMethod(method), endpoint);
|
||||
if (body is not null) {
|
||||
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||
}
|
||||
HttpResponseMessage response = await HttpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
string responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
#else
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
#endif
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
return responseBody;
|
||||
} else {
|
||||
throw new InvalidOperationException($"Failed to invoke endpoint {endpointUri} with status code {response.StatusCode}{Environment.NewLine}{responseBody}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory, string? serviceUrl,
|
||||
RuntimeOs os, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -164,7 +205,10 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
var endpoint = GetEndpoint("v1/releaseGroups/create", velopackBaseUrl);
|
||||
var response = await HttpClient.PostAsJsonAsync(endpoint, request, cancellationToken);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
if (!response.IsSuccessStatusCode) {
|
||||
string content = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
throw new InvalidOperationException($"Failed to create release group with version {version.ToNormalizedString()}{Environment.NewLine}Response status code: {response.StatusCode}{Environment.NewLine}{content}");
|
||||
}
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<ReleaseGroup>(cancellationToken: cancellationToken)
|
||||
?? throw new InvalidOperationException($"Failed to create release group with version {version.ToNormalizedString()}");
|
||||
@@ -182,7 +226,7 @@ public class VelopackFlowServiceClient(HttpClient HttpClient, ILogger Logger) :
|
||||
var latestPath = Path.Combine(releaseDirectory, fileName);
|
||||
|
||||
using var fileStream = File.OpenRead(latestPath);
|
||||
|
||||
|
||||
using var fileContent = new StreamContent(fileStream);
|
||||
formData.Add(fileContent, "File", fileName);
|
||||
|
||||
|
||||
@@ -36,5 +36,10 @@ public static class HttpClientExtensions
|
||||
var json = await content.ReadAsStringAsync();
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<TValue>(json);
|
||||
}
|
||||
|
||||
public static async Task<string> ReadAsStringAsync(this HttpContent content, CancellationToken _)
|
||||
{
|
||||
return await content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
39
src/vpk/Velopack.Vpk/Commands/Flow/ApiCommand.cs
Normal file
39
src/vpk/Velopack.Vpk/Commands/Flow/ApiCommand.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Net.Http;
|
||||
using Serilog.Core;
|
||||
|
||||
namespace Velopack.Vpk.Commands.Flow;
|
||||
public class ApiCommand : VelopackServiceCommand
|
||||
{
|
||||
public string Method { get; private set; } = "";
|
||||
|
||||
public string Endpoint { get; private set; } = "";
|
||||
|
||||
public string? Body { get; private set; }
|
||||
|
||||
public ApiCommand()
|
||||
: base("api", "Invoke velopack flow API endpoints")
|
||||
{
|
||||
AddOption<string>(v => Method = v, "--method", "-m")
|
||||
.SetDescription("The HTTP method for the endpoint")
|
||||
.SetArgumentHelpName("METHOD")
|
||||
.SetRequired()
|
||||
.SetDefault(HttpMethod.Get.Method);
|
||||
|
||||
AddOption<string>(v => Endpoint = v, "--endpoint", "-e")
|
||||
.SetDescription("The relative URI for the endpoint")
|
||||
.SetArgumentHelpName("URI")
|
||||
.SetRequired();
|
||||
|
||||
AddOption<string>(v => Body = v, "--body", "-b")
|
||||
.SetDescription("The body of the HTTP message")
|
||||
.SetArgumentHelpName("BODY");
|
||||
}
|
||||
|
||||
public override void Initialize(LoggingLevelSwitch logLevelSwitch)
|
||||
{
|
||||
base.Initialize(logLevelSwitch);
|
||||
logLevelSwitch.MinimumLevel = Serilog.Events.LogEventLevel.Warning;
|
||||
}
|
||||
}
|
||||
26
src/vpk/Velopack.Vpk/Commands/Flow/ApiCommandRunner.cs
Normal file
26
src/vpk/Velopack.Vpk/Commands/Flow/ApiCommandRunner.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Threading;
|
||||
using Serilog.Core;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
using Velopack.Packaging.Flow;
|
||||
|
||||
namespace Velopack.Vpk.Commands.Flow;
|
||||
public class ApiCommandRunner(IVelopackFlowServiceClient Client) : ICommand<ApiOptions>
|
||||
{
|
||||
public async Task Run(ApiOptions options)
|
||||
{
|
||||
CancellationToken token = CancellationToken.None;
|
||||
if (!await Client.LoginAsync(new VelopackLoginOptions() {
|
||||
AllowCacheCredentials = true,
|
||||
AllowDeviceCodeFlow = false,
|
||||
AllowInteractiveLogin = false,
|
||||
ApiKey = options.ApiKey,
|
||||
VelopackBaseUrl = options.VelopackBaseUrl
|
||||
}, true, token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string response = await Client.InvokeEndpointAsync(options, options.Endpoint, options.Method, options.Body, token);
|
||||
Console.WriteLine(response);
|
||||
}
|
||||
}
|
||||
|
||||
11
src/vpk/Velopack.Vpk/Commands/Flow/ApiOptions.cs
Normal file
11
src/vpk/Velopack.Vpk/Commands/Flow/ApiOptions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
#nullable enable
|
||||
using Velopack.Packaging.Flow;
|
||||
|
||||
namespace Velopack.Vpk.Commands.Flow;
|
||||
|
||||
public sealed class ApiOptions : VelopackServiceOptions
|
||||
{
|
||||
public string Endpoint { get; set; } = "";
|
||||
public string Method { get; set; } = "";
|
||||
public string? Body { get; set; }
|
||||
}
|
||||
@@ -12,6 +12,6 @@ public class LoginCommandRunner(IVelopackFlowServiceClient Client) : ICommand<Lo
|
||||
await Client.LoginAsync(new() {
|
||||
VelopackBaseUrl = options.VelopackBaseUrl,
|
||||
ApiKey = options.ApiKey,
|
||||
}, CancellationToken.None);
|
||||
}, false, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class PublishCommandRunner(IVelopackFlowServiceClient Client) : ICommand<
|
||||
AllowInteractiveLogin = false,
|
||||
ApiKey = options.ApiKey,
|
||||
VelopackBaseUrl = options.VelopackBaseUrl
|
||||
}, token)) {
|
||||
}, false, token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog.Core;
|
||||
|
||||
namespace Velopack.Vpk.Commands;
|
||||
|
||||
@@ -70,6 +71,9 @@ public class BaseCommand : CliCommand
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Initialize(LoggingLevelSwitch logLevelSwitch)
|
||||
{ }
|
||||
|
||||
public ParseResult ParseAndApply(string command, IConfiguration config = null, RuntimeOs? targetOs = null)
|
||||
{
|
||||
var x = Parse(command);
|
||||
|
||||
@@ -7,11 +7,11 @@ using Spectre.Console.Rendering;
|
||||
|
||||
namespace Velopack.Vpk.Logging;
|
||||
|
||||
public class MySpectreConsoleSink : ILogEventSink
|
||||
public class SpectreConsoleSink : ILogEventSink
|
||||
{
|
||||
private readonly string _dirtmp;
|
||||
|
||||
public MySpectreConsoleSink()
|
||||
public SpectreConsoleSink()
|
||||
{
|
||||
_dirtmp = Path.GetTempPath();
|
||||
}
|
||||
@@ -53,13 +53,13 @@ public class MySpectreConsoleSink : ILogEventSink
|
||||
}
|
||||
}
|
||||
|
||||
public static class MySpectreConsoleSinkExtensions
|
||||
public static class SpectreConsoleSinkExtensions
|
||||
{
|
||||
public static LoggerConfiguration Spectre(
|
||||
this LoggerSinkConfiguration loggerConfiguration,
|
||||
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
|
||||
LoggingLevelSwitch levelSwitch = null)
|
||||
{
|
||||
return loggerConfiguration.Sink(new MySpectreConsoleSink(), restrictedToMinimumLevel, levelSwitch);
|
||||
return loggerConfiguration.Sink(new SpectreConsoleSink(), restrictedToMinimumLevel, levelSwitch);
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public static partial class OptionMapper
|
||||
public static partial LoginOptions ToOptions(this LoginCommand cmd);
|
||||
public static partial LogoutOptions ToOptions(this LogoutCommand cmd);
|
||||
public static partial PublishOptions ToOptions(this PublishCommand cmd);
|
||||
public static partial ApiOptions ToOptions(this ApiCommand cmd);
|
||||
|
||||
private static DirectoryInfo StringToDirectoryInfo(string t)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Velopack.Deployment;
|
||||
using Velopack.Packaging.Abstractions;
|
||||
@@ -156,6 +157,10 @@ public class Program
|
||||
HideCommand(rootCommand.AddCommand<LogoutCommand, LogoutCommandRunner, LogoutOptions>(provider));
|
||||
HideCommand(rootCommand.AddCommand<PublishCommand, PublishCommandRunner, PublishOptions>(provider));
|
||||
|
||||
var flowCommand = new CliCommand("flow", "Commands for interacting with Velopack Flow.") { Hidden = true };
|
||||
HideCommand(flowCommand.AddCommand<ApiCommand, ApiCommandRunner, ApiOptions>(provider));
|
||||
rootCommand.Add(flowCommand);
|
||||
|
||||
var cli = new CliConfiguration(rootCommand);
|
||||
return await cli.InvokeAsync(args);
|
||||
|
||||
@@ -173,8 +178,11 @@ public class Program
|
||||
|
||||
private static void SetupLogging(IHostApplicationBuilder builder, bool verbose, bool legacyConsole)
|
||||
{
|
||||
var levelSwitch = new LoggingLevelSwitch {
|
||||
MinimumLevel = verbose ? LogEventLevel.Debug : LogEventLevel.Information
|
||||
};
|
||||
var conf = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(verbose ? LogEventLevel.Debug : LogEventLevel.Information)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System", LogEventLevel.Warning);
|
||||
|
||||
@@ -186,6 +194,7 @@ public class Program
|
||||
builder.Services.AddSingleton<IFancyConsole, SpectreConsole>();
|
||||
conf.WriteTo.Spectre();
|
||||
}
|
||||
builder.Services.AddSingleton(levelSwitch);
|
||||
builder.Services.AddSingleton<IConsole>(sp => sp.GetRequiredService<IFancyConsole>());
|
||||
|
||||
Log.Logger = conf.CreateLogger();
|
||||
@@ -247,6 +256,9 @@ public static class ProgramCommandExtensions
|
||||
var console = provider.GetRequiredService<IFancyConsole>();
|
||||
var config = provider.GetRequiredService<IConfiguration>();
|
||||
var defaults = provider.GetRequiredService<VelopackDefaults>();
|
||||
var logLevelSwitch = provider.GetRequiredService<LoggingLevelSwitch>();
|
||||
|
||||
command.Initialize(logLevelSwitch);
|
||||
|
||||
logger.LogInformation($"[bold]{Program.INTRO}[/]");
|
||||
var updateCheck = new UpdateChecker(logger);
|
||||
|
||||
Reference in New Issue
Block a user