mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Implementing Flow tiered rollout in C#
This add support for setting the tiered rollout percentage in vpk
This commit is contained in:
@@ -154,7 +154,7 @@ namespace Velopack.Locators
|
||||
{
|
||||
if (PackagesDir == null) return null;
|
||||
var stagedUserIdFile = Path.Combine(PackagesDir, ".betaId");
|
||||
var ret = default(Guid);
|
||||
Guid ret;
|
||||
|
||||
if (File.Exists(stagedUserIdFile)) {
|
||||
try {
|
||||
@@ -170,13 +170,9 @@ namespace Velopack.Locators
|
||||
Log.Warn($"No userId could not be parsed from '{stagedUserIdFile}', creating a new one.");
|
||||
}
|
||||
|
||||
var prng = new Random();
|
||||
var buf = new byte[4096];
|
||||
prng.NextBytes(buf);
|
||||
|
||||
ret = GuidUtil.CreateGuidFromHash(buf);
|
||||
ret = Guid.NewGuid();
|
||||
try {
|
||||
File.WriteAllText(stagedUserIdFile, ret.ToString(), Encoding.UTF8);
|
||||
File.WriteAllText(stagedUserIdFile, ret.ToString("N"), Encoding.UTF8);
|
||||
Log.Info($"Generated new staging userId: {ret}");
|
||||
return ret;
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -32,8 +32,14 @@ namespace Velopack.Sources
|
||||
public async Task<VelopackAssetFeed> GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null,
|
||||
VelopackAsset? latestLocalRelease = null)
|
||||
{
|
||||
string? packageId = latestLocalRelease?.PackageId ?? VelopackLocator.GetDefault(logger).AppId;
|
||||
if (string.IsNullOrWhiteSpace(packageId)) {
|
||||
//Without a package id, we can't get a feed.
|
||||
return new VelopackAssetFeed();
|
||||
}
|
||||
|
||||
Uri baseUri = new(BaseUri, $"v1.0/manifest/");
|
||||
var uri = HttpUtil.AppendPathToUri(baseUri, CoreUtil.GetVeloReleaseIndexName(channel));
|
||||
Uri uri = HttpUtil.AppendPathToUri(baseUri, $"{packageId}/{channel}");
|
||||
var args = new Dictionary<string, string>();
|
||||
|
||||
if (VelopackRuntimeInfo.SystemArch != RuntimeCpu.Unknown) {
|
||||
@@ -46,10 +52,11 @@ namespace Velopack.Sources
|
||||
}
|
||||
|
||||
if (latestLocalRelease != null) {
|
||||
args.Add("id", latestLocalRelease.PackageId);
|
||||
args.Add("localVersion", latestLocalRelease.Version.ToString());
|
||||
} else {
|
||||
args.Add("id", VelopackLocator.GetDefault(logger).AppId ?? "");
|
||||
}
|
||||
|
||||
if (stagingId != null) {
|
||||
args.Add("stagingId", stagingId.Value.ToString());
|
||||
}
|
||||
|
||||
var uriAndQuery = HttpUtil.AddQueryParamsToUri(uri, args);
|
||||
|
||||
@@ -122,11 +122,11 @@ namespace Velopack
|
||||
{
|
||||
EnsureInstalled();
|
||||
var installedVer = CurrentVersion!;
|
||||
var betaId = Locator.GetOrCreateStagedUserId();
|
||||
var stagedUserId = Locator.GetOrCreateStagedUserId();
|
||||
var latestLocalFull = Locator.GetLatestLocalFullPackage();
|
||||
|
||||
Log.Debug("Retrieving latest release feed.");
|
||||
var feedObj = await Source.GetReleaseFeed(Log, Channel, betaId, latestLocalFull).ConfigureAwait(false);
|
||||
var feedObj = await Source.GetReleaseFeed(Log, Channel, stagedUserId, latestLocalFull).ConfigureAwait(false);
|
||||
var feed = feedObj.Assets;
|
||||
|
||||
var latestRemoteFull = feed.Where(r => r.Type == VelopackAssetType.Full).MaxByPolyfill(x => x.Version).FirstOrDefault();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -104,7 +104,7 @@ namespace Velopack.Util
|
||||
return ExtremaBy(source, keySelector, (key, minValue) => comparer.Compare(key, minValue));
|
||||
}
|
||||
|
||||
private static IList<TSource> ExtremaBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, TKey, int> compare)
|
||||
private static List<TSource> ExtremaBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, TKey, int> compare)
|
||||
{
|
||||
var result = new List<TSource>();
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PublishTask : MSBuildAsyncTask
|
||||
// todo: currently it's not possible to cross-compile for different OSes using Velopack.Build
|
||||
var targetOs = VelopackRuntimeInfo.SystemOs;
|
||||
|
||||
await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, targetOs, WaitForLive, cancellationToken)
|
||||
await client.UploadLatestReleaseAssetsAsync(Channel, ReleaseDirectory, targetOs, WaitForLive, 100, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,6 +24,7 @@ public class PublishCommandRunner(ILogger logger, IFancyConsole console) : IComm
|
||||
options.ReleaseDirectory,
|
||||
options.TargetOs,
|
||||
options.WaitForLive,
|
||||
options.TieredRolloutPercentage,
|
||||
token);
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,6 @@ public sealed class PublishOptions : VelopackFlowServiceOptions
|
||||
public string? Channel { get; set; }
|
||||
|
||||
public bool WaitForLive { get; set; }
|
||||
|
||||
public int TieredRolloutPercentage { get; set; }
|
||||
}
|
||||
@@ -1589,14 +1589,14 @@ namespace Velopack.Flow
|
||||
}
|
||||
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetManifestAsync(string packageId, string channel)
|
||||
public virtual System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetManifestAsync(string packageId, string channel, string id, string stagingId, string arch, string os, string rid, string localVersion)
|
||||
{
|
||||
return GetManifestAsync(packageId, channel, System.Threading.CancellationToken.None);
|
||||
return GetManifestAsync(packageId, channel, id, stagingId, arch, os, rid, localVersion, System.Threading.CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetManifestAsync(string packageId, string channel, System.Threading.CancellationToken cancellationToken)
|
||||
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetManifestAsync(string packageId, string channel, string id, string stagingId, string arch, string os, string rid, string localVersion, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (packageId == null)
|
||||
throw new System.ArgumentNullException("packageId");
|
||||
@@ -1620,6 +1620,32 @@ namespace Velopack.Flow
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(packageId, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
urlBuilder_.Append('/');
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(channel, System.Globalization.CultureInfo.InvariantCulture)));
|
||||
urlBuilder_.Append('?');
|
||||
if (id != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (stagingId != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("StagingId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(stagingId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (arch != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Arch")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(arch, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (os != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Os")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(os, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (rid != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Rid")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(rid, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (localVersion != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("LocalVersion")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(localVersion, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
urlBuilder_.Length--;
|
||||
|
||||
PrepareRequest(client_, request_, urlBuilder_);
|
||||
|
||||
@@ -1684,14 +1710,14 @@ namespace Velopack.Flow
|
||||
}
|
||||
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetChannelManifestAsync(string channel, string id, string arch, string os, string rid, string localVersion)
|
||||
public virtual System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetChannelManifestAsync(string channel, string id, string stagingId, string arch, string os, string rid, string localVersion)
|
||||
{
|
||||
return GetChannelManifestAsync(channel, id, arch, os, rid, localVersion, System.Threading.CancellationToken.None);
|
||||
return GetChannelManifestAsync(channel, id, stagingId, arch, os, rid, localVersion, System.Threading.CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetChannelManifestAsync(string channel, string id, string arch, string os, string rid, string localVersion, System.Threading.CancellationToken cancellationToken)
|
||||
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.ICollection<VelopackAsset>> GetChannelManifestAsync(string channel, string id, string stagingId, string arch, string os, string rid, string localVersion, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (channel == null)
|
||||
throw new System.ArgumentNullException("channel");
|
||||
@@ -1716,6 +1742,10 @@ namespace Velopack.Flow
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (stagingId != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("StagingId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(stagingId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
if (arch != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("Arch")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(arch, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
@@ -3841,14 +3871,14 @@ namespace Velopack.Flow
|
||||
}
|
||||
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual System.Threading.Tasks.Task<ReleaseGroupListResponse> ListReleaseGroupsAsync(System.Guid projectId)
|
||||
public virtual System.Threading.Tasks.Task<ReleaseGroupListResponse> ListReleaseGroupsAsync(System.Guid projectId, string channelId)
|
||||
{
|
||||
return ListReleaseGroupsAsync(projectId, System.Threading.CancellationToken.None);
|
||||
return ListReleaseGroupsAsync(projectId, channelId, System.Threading.CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <exception cref="ApiException">A server side error occurred.</exception>
|
||||
public virtual async System.Threading.Tasks.Task<ReleaseGroupListResponse> ListReleaseGroupsAsync(System.Guid projectId, System.Threading.CancellationToken cancellationToken)
|
||||
public virtual async System.Threading.Tasks.Task<ReleaseGroupListResponse> ListReleaseGroupsAsync(System.Guid projectId, string channelId, System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
if (projectId == null)
|
||||
throw new System.ArgumentNullException("projectId");
|
||||
@@ -3868,6 +3898,10 @@ namespace Velopack.Flow
|
||||
urlBuilder_.Append("v1/releaseGroups/list");
|
||||
urlBuilder_.Append('?');
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("projectId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(projectId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
if (channelId != null)
|
||||
{
|
||||
urlBuilder_.Append(System.Uri.EscapeDataString("channelId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(channelId, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
|
||||
}
|
||||
urlBuilder_.Length--;
|
||||
|
||||
PrepareRequest(client_, request_, urlBuilder_);
|
||||
@@ -4094,6 +4128,12 @@ namespace Velopack.Flow
|
||||
throw new ApiException("A server side error occurred.", status_, responseText_, headers_, null);
|
||||
}
|
||||
else
|
||||
if (status_ == 400)
|
||||
{
|
||||
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new ApiException("A server side error occurred.", status_, responseText_, headers_, null);
|
||||
}
|
||||
else
|
||||
if (status_ == 401)
|
||||
{
|
||||
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
@@ -4269,6 +4309,12 @@ namespace Velopack.Flow
|
||||
return objectResponse_.Object;
|
||||
}
|
||||
else
|
||||
if (status_ == 400)
|
||||
{
|
||||
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new ApiException("A server side error occurred.", status_, responseText_, headers_, null);
|
||||
}
|
||||
else
|
||||
if (status_ == 401)
|
||||
{
|
||||
string responseText_ = ( response_.Content == null ) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
@@ -4891,6 +4937,9 @@ namespace Velopack.Flow
|
||||
[Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public string Version { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public VelopackAssetType Type { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("size", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public long Size { get; set; }
|
||||
|
||||
@@ -4911,6 +4960,24 @@ namespace Velopack.Flow
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public enum VelopackAssetType
|
||||
{
|
||||
|
||||
Unknown = 0,
|
||||
|
||||
Full = 1,
|
||||
|
||||
Delta = 2,
|
||||
|
||||
Portable = 3,
|
||||
|
||||
Setup = 4,
|
||||
|
||||
MsiDeploymentTool = 5,
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class ReleaseAssetListResponse
|
||||
{
|
||||
@@ -5265,6 +5332,12 @@ namespace Velopack.Flow
|
||||
[Newtonsoft.Json.JsonProperty("channelName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public string ChannelName { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("packageId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public string PackageId { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("tieredRolloutPercentage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public double TieredRolloutPercentage { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("createdAt", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public System.DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
@@ -5406,6 +5479,10 @@ namespace Velopack.Flow
|
||||
[Newtonsoft.Json.JsonProperty("channelIdentifier", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
public string ChannelIdentifier { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("tieredRolloutPercentage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
[System.ComponentModel.DataAnnotations.Range(0.0D, 1.0D)]
|
||||
public double TieredRolloutPercentage { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
@@ -5421,6 +5498,10 @@ namespace Velopack.Flow
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public ReleaseGroupPublishState? State { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("tieredRolloutPercentage", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
|
||||
[System.ComponentModel.DataAnnotations.Range(0.0D, 1.0D)]
|
||||
public double? TieredRolloutPercentage { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
|
||||
@@ -38,19 +38,15 @@ public static class FlowApiExtensions
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FileType ToFileType(this VelopackAssetType type)
|
||||
public static FileType ToFileType(this Velopack.VelopackAssetType type)
|
||||
{
|
||||
switch (type) {
|
||||
case VelopackAssetType.Full:
|
||||
return FileType.Full;
|
||||
case VelopackAssetType.Delta:
|
||||
return FileType.Delta;
|
||||
case VelopackAssetType.Portable:
|
||||
return FileType.Portable;
|
||||
case VelopackAssetType.Installer:
|
||||
return FileType.Setup;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
return type switch {
|
||||
Velopack.VelopackAssetType.Full => FileType.Full,
|
||||
Velopack.VelopackAssetType.Delta => FileType.Delta,
|
||||
Velopack.VelopackAssetType.Portable => FileType.Portable,
|
||||
Velopack.VelopackAssetType.Installer => FileType.Setup,
|
||||
//TODO: MSI Deployment Tool?
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -125,13 +125,13 @@ public class VelopackFlowServiceClient(
|
||||
}
|
||||
|
||||
public async Task UploadLatestReleaseAssetsAsync(string? channel, string releaseDirectory,
|
||||
RuntimeOs os, bool waitForLive, CancellationToken cancellationToken)
|
||||
RuntimeOs os, bool waitForLive, int tieredRolloutPercentage, CancellationToken cancellationToken)
|
||||
{
|
||||
AssertAuthenticated();
|
||||
|
||||
channel ??= DefaultName.GetDefaultChannel(os);
|
||||
BuildAssets assets = BuildAssets.Read(releaseDirectory, channel);
|
||||
var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == VelopackAssetType.Full);
|
||||
var fullAsset = assets.GetReleaseEntries().SingleOrDefault(a => a.Type == Velopack.VelopackAssetType.Full);
|
||||
|
||||
if (fullAsset is null) {
|
||||
Logger.LogError("No full asset found in release directory {ReleaseDirectory} (or it's missing from assets file)", releaseDirectory);
|
||||
@@ -143,7 +143,7 @@ public class VelopackFlowServiceClient(
|
||||
var version = fullAsset.Version;
|
||||
|
||||
var filesToUpload = assets.GetAssets()
|
||||
.Where(p => p.Type is not VelopackAssetType.Delta)
|
||||
.Where(p => p.Type is not Velopack.VelopackAssetType.Delta)
|
||||
.Select(p => (p.Path, p.Type.ToFileType()))
|
||||
.ToArray();
|
||||
|
||||
@@ -160,7 +160,7 @@ public class VelopackFlowServiceClient(
|
||||
report(-1);
|
||||
await CreateChannelIfNotExists(client, packageId, channel, cancellationToken);
|
||||
report(50);
|
||||
var result = await CreateReleaseGroupAsync(client, packageId, version, channel, cancellationToken);
|
||||
var result = await CreateReleaseGroupAsync(client, packageId, version, channel, tieredRolloutPercentage / 100.0, cancellationToken);
|
||||
Logger.LogInformation("Created release {Version} ({ReleaseGroupId})", version, result.Id);
|
||||
report(100);
|
||||
return result;
|
||||
@@ -316,12 +316,13 @@ public class VelopackFlowServiceClient(
|
||||
}
|
||||
|
||||
private static async Task<ReleaseGroup> CreateReleaseGroupAsync(FlowApi client, string packageId, SemanticVersion version, string channel,
|
||||
CancellationToken cancellationToken)
|
||||
double tieredRolloutPercentage, CancellationToken cancellationToken)
|
||||
{
|
||||
CreateReleaseGroupRequest request = new() {
|
||||
ChannelIdentifier = channel,
|
||||
PackageId = packageId,
|
||||
Version = version.ToNormalizedString()
|
||||
Version = version.ToNormalizedString(),
|
||||
TieredRolloutPercentage = tieredRolloutPercentage
|
||||
};
|
||||
|
||||
return await client.CreateReleaseGroupAsync(request, cancellationToken);
|
||||
|
||||
@@ -9,6 +9,8 @@ public class PublishCommand : VelopackServiceCommand
|
||||
|
||||
public bool WaitForLive { get; set; }
|
||||
|
||||
public int TieredRolloutPercentage { get; set; }
|
||||
|
||||
public PublishCommand()
|
||||
: base("publish", "Uploads a release to Velopack's hosted service")
|
||||
{
|
||||
@@ -23,5 +25,10 @@ public class PublishCommand : VelopackServiceCommand
|
||||
|
||||
AddOption<bool>(v => WaitForLive = v, "--waitForLive")
|
||||
.SetDescription("Wait for the release to finish processing and go live.");
|
||||
|
||||
AddOption<int>(v => TieredRolloutPercentage = v, "--tieredRolloutPercentage")
|
||||
.SetDescription("Set the starting percentage for this release when using a tiered rollout. Range 0 to 100")
|
||||
.SetDefault(100)
|
||||
.SetValidRange(0, 100);
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,12 @@ internal static class SystemCommandLineExtensions
|
||||
return option;
|
||||
}
|
||||
|
||||
public static CliOption<int> SetValidRange(this CliOption<int> option, int minimum, int maximum)
|
||||
{
|
||||
option.Validators.Add(x => Validate.MustBeBetween(x, minimum, maximum));
|
||||
return option;
|
||||
}
|
||||
|
||||
public static CliOption<T> SetArgumentHelpName<T>(this CliOption<T> option, string argumentHelpName)
|
||||
{
|
||||
option.HelpName = argumentHelpName;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using System.Text;
|
||||
using Velopack.Sources;
|
||||
|
||||
namespace Velopack.Tests;
|
||||
|
||||
public class FakeDownloader : Sources.IFileDownloader
|
||||
public class FakeDownloader : IFileDownloader
|
||||
{
|
||||
public string LastUrl { get; private set; }
|
||||
public string LastLocalFile { get; private set; }
|
||||
public string LastAuthHeader { get; private set; }
|
||||
public string LastAcceptHeader { get; private set; }
|
||||
public byte[] MockedResponseBytes { get; set; } = new byte[0];
|
||||
public byte[] MockedResponseBytes { get; set; } = [];
|
||||
public bool WriteMockLocalFile { get; set; } = false;
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string auth, string acc, double timeout = 30)
|
||||
|
||||
@@ -7,7 +7,7 @@ using Velopack.Util;
|
||||
|
||||
namespace Velopack.Tests.TestHelpers;
|
||||
|
||||
internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
internal class FakeFixtureRepository : IFileDownloader
|
||||
{
|
||||
private readonly string _pkgId;
|
||||
private readonly IEnumerable<ReleaseEntry> _releases;
|
||||
|
||||
@@ -19,29 +19,30 @@
|
||||
</Choose>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\src\vpk\Velopack.Core\SimpleJson.cs" Link="SimpleJson.cs"/>
|
||||
<Compile Include="..\..\src\vpk\Velopack.Core\SimpleJson.cs" Link="SimpleJson.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.2"/>
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('net4')) ">
|
||||
<Reference Include="System.Web"/>
|
||||
<Reference Include="System.Net.Http"/>
|
||||
<Reference Include="System.IO.Compression"/>
|
||||
<Reference Include="System.IO.Compression.FileSystem"/>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
</ItemGroup>
|
||||
|
||||
<Choose>
|
||||
<When Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" SetTargetFramework="TargetFramework=netstandard2.0"/>
|
||||
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" SetTargetFramework="TargetFramework=netstandard2.0" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj"/>
|
||||
<ProjectReference Include="..\..\src\lib-csharp\Velopack.csproj" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
|
||||
Reference in New Issue
Block a user