fix: S3 repo with no session throws, added tests for B2

This commit is contained in:
Caelan Sayler
2024-01-24 10:45:06 +00:00
parent 015a10ccc1
commit 8aab325cd1
4 changed files with 214 additions and 100 deletions

View File

@@ -45,7 +45,6 @@ public class S3Repository : DownRepository<S3DownloadOptions>, IRepositoryCanUpl
Log.Info($"There are {remoteReleases.Assets.Length} assets in remote RELEASES file.");
var localEntries = build.GetReleaseEntries();
var releaseEntries = ReleaseEntryHelper.MergeAssets(localEntries, remoteReleases.Assets).ToArray();
Log.Info($"{releaseEntries.Length} merged releases.");
@@ -58,6 +57,18 @@ public class S3Repository : DownRepository<S3DownloadOptions>, IRepositoryCanUpl
File.WriteAllText(tmpReleases, ReleaseEntryHelper.GetAssetFeedJson(new VelopackAssetFeed { Assets = releaseEntries }));
var releasesName = Utility.GetVeloReleaseIndexName(options.Channel);
await UploadFile(client, options.Bucket, releasesName, new FileInfo(tmpReleases), true);
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
var legacyKey = Utility.GetReleasesFileName(options.Channel);
using var _2 = Utility.GetTempFileName(out var tmpReleases2);
using (var fs = File.Create(tmpReleases2)) {
ReleaseEntry.WriteReleaseFile(releaseEntries.Select(ReleaseEntry.FromVelopackAsset), fs);
}
await UploadFile(client, options.Bucket, legacyKey, new FileInfo(tmpReleases2), true);
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0612 // Type or member is obsolete
Log.Info("Done.");
}
@@ -94,18 +105,20 @@ public class S3Repository : DownRepository<S3DownloadOptions>, IRepositoryCanUpl
private static AmazonS3Client GetS3Client(S3DownloadOptions options)
{
if (options.Region != null) {
var r = RegionEndpoint.GetBySystemName(options.Region);
if (string.IsNullOrWhiteSpace(options.KeyId)) {
return new AmazonS3Client(r);
}
return new AmazonS3Client(options.KeyId, options.Secret, options.Session, r);
} else if (options.Endpoint != null) {
var config = new AmazonS3Config() { ServiceURL = options.Endpoint };
return new AmazonS3Client(options.KeyId, options.Secret, options.Session, config);
var config = new AmazonS3Config() { ServiceURL = options.Endpoint };
if (options.Endpoint != null) {
config.ServiceURL = options.Endpoint;
} else if (options.Region != null) {
config.RegionEndpoint = RegionEndpoint.GetBySystemName(options.Region);
} else {
throw new InvalidOperationException("Missing endpoint");
}
if (options.Session != null) {
return new AmazonS3Client(options.KeyId, options.Secret, options.Session, config);
} else {
return new AmazonS3Client(options.KeyId, options.Secret, config);
}
}
private async Task UploadFile(AmazonS3Client client, string bucket, string key, FileInfo f, bool overwriteRemote)
@@ -120,13 +133,13 @@ public class S3Repository : DownRepository<S3DownloadOptions>, IRepositoryCanUpl
if (stored != null) {
if (stored.Equals(md5, StringComparison.InvariantCultureIgnoreCase)) {
Log.Info($"Upload file '{f.Name}' skipped (already exists in remote)");
Log.Info($"Upload file '{key}' skipped (already exists in remote)");
return;
} else if (overwriteRemote) {
Log.Info($"File '{f.Name}' exists in remote, replacing...");
Log.Info($"File '{key}' exists in remote, replacing...");
deleteOldVersionId = metadata.VersionId;
} else {
Log.Warn($"File '{f.Name}' exists in remote and checksum does not match local file. Use 'overwrite' argument to replace remote file.");
Log.Warn($"File '{key}' exists in remote and checksum does not match local file. Use 'overwrite' argument to replace remote file.");
return;
}
}
@@ -141,12 +154,12 @@ public class S3Repository : DownRepository<S3DownloadOptions>, IRepositoryCanUpl
Key = key,
};
await RetryAsync(() => client.PutObjectAsync(req), "Uploading " + f.Name);
await RetryAsync(() => client.PutObjectAsync(req), "Uploading " + key);
if (deleteOldVersionId != null) {
try {
await RetryAsync(() => client.DeleteObjectAsync(bucket, key, deleteOldVersionId),
"Removing old version of " + f.Name);
"Removing old version of " + key);
} catch { }
}
}

View File

@@ -8,14 +8,14 @@ using Velopack.Packaging.Exceptions;
namespace Velopack.Packaging.Tests
{
public class DeploymentTests
public class GithubDeploymentTests
{
public readonly static string GITHUB_TOKEN = Environment.GetEnvironmentVariable("VELOPACK_GITHUB_TEST_TOKEN");
public readonly static string GITHUB_REPOURL = "https://github.com/caesay/VelopackGithubUpdateTest";
private readonly ITestOutputHelper _output;
public DeploymentTests(ITestOutputHelper output)
public GithubDeploymentTests(ITestOutputHelper output)
{
_output = output;
}
@@ -24,12 +24,12 @@ namespace Velopack.Packaging.Tests
public void WillRefuseToUploadMultipleWithoutMergeArg()
{
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
using var logger = _output.BuildLoggerFor<DeploymentTests>();
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
using var _1 = Utility.GetTempDirectory(out var releaseDir);
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
using var ghvar = GitHubReleaseTest.Create("nomerge", logger);
var id = "GithubUpdateTest";
PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
var gh = new GitHubRepository(logger);
var options = new GitHubUploadOptions {
@@ -43,7 +43,7 @@ namespace Velopack.Packaging.Tests
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
options.ReleaseDir = new DirectoryInfo(releaseDir2);
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
@@ -53,12 +53,12 @@ namespace Velopack.Packaging.Tests
public void WillNotMergeMixmatchedTag()
{
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
using var logger = _output.BuildLoggerFor<DeploymentTests>();
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
using var _1 = Utility.GetTempDirectory(out var releaseDir);
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
using var ghvar = GitHubReleaseTest.Create("mixmatched", logger);
var id = "GithubUpdateTest";
PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
var gh = new GitHubRepository(logger);
var options = new GitHubUploadOptions {
@@ -73,7 +73,7 @@ namespace Velopack.Packaging.Tests
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
TestApp.PackTestApp(id, $"0.0.2-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger);
options.ReleaseDir = new DirectoryInfo(releaseDir2);
Assert.ThrowsAny<UserInfoException>(() => gh.UploadMissingAssetsAsync(options).GetAwaiterResult());
@@ -83,12 +83,12 @@ namespace Velopack.Packaging.Tests
public void WillMergeGithubReleases()
{
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
using var logger = _output.BuildLoggerFor<DeploymentTests>();
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
using var _1 = Utility.GetTempDirectory(out var releaseDir);
using var _2 = Utility.GetTempDirectory(out var releaseDir2);
using var ghvar = GitHubReleaseTest.Create("yesmerge", logger);
var id = "GithubUpdateTest";
PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir, logger);
var gh = new GitHubRepository(logger);
var options = new GitHubUploadOptions {
@@ -104,7 +104,7 @@ namespace Velopack.Packaging.Tests
gh.UploadMissingAssetsAsync(options).GetAwaiterResult();
PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger, channel: "experimental");
TestApp.PackTestApp(id, $"0.0.1-{ghvar.UniqueSuffix}", "t1", releaseDir2, logger, channel: "experimental");
options.ReleaseDir = new DirectoryInfo(releaseDir2);
options.Channel = "experimental";
@@ -115,7 +115,7 @@ namespace Velopack.Packaging.Tests
public void CanDeployAndUpdateFromGithub()
{
Skip.If(String.IsNullOrWhiteSpace(GITHUB_TOKEN), "VELOPACK_GITHUB_TEST_TOKEN is not set.");
using var logger = _output.BuildLoggerFor<DeploymentTests>();
using var logger = _output.BuildLoggerFor<GithubDeploymentTests>();
var id = "GithubUpdateTest";
using var _1 = Utility.GetTempDirectory(out var releaseDir);
var (repoOwner, repoName) = GitHubRepository.GetOwnerAndRepo(GITHUB_REPOURL);
@@ -136,8 +136,8 @@ This is just a _test_!
throw new Exception("VELOPACK_GITHUB_TEST_TOKEN is not set.");
var newVer = $"{VelopackRuntimeInfo.VelopackNugetVersion}";
PackTestApp(id, $"0.0.1", "t1", releaseDir, logger, notesPath, channel: uniqueSuffix);
PackTestApp(id, newVer, "t2", releaseDir, logger, notesPath, channel: uniqueSuffix);
TestApp.PackTestApp(id, $"0.0.1", "t1", releaseDir, logger, notesPath, channel: uniqueSuffix);
TestApp.PackTestApp(id, newVer, "t2", releaseDir, logger, notesPath, channel: uniqueSuffix);
// deploy
var gh = new GitHubRepository(logger);
@@ -226,77 +226,6 @@ This is just a _test_!
}
}
private void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
string releaseNotes = null, string channel = null)
{
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");
var oldText = File.ReadAllText(testStringFile);
try {
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
var psi = new ProcessStartInfo("dotnet");
psi.WorkingDirectory = projDir;
psi.AppendArgumentListSafe(args, out var debug);
logger.Info($"TEST: Running {psi.FileName} {debug}");
using var p = Process.Start(psi);
p.WaitForExit();
if (p.ExitCode != 0)
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
if (VelopackRuntimeInfo.IsWindows) {
var options = new WindowsPackOptions {
EntryExecutableName = "TestApp.exe",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new WindowsPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsOSX) {
var options = new OsxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.icns"),
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new OsxPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsLinux) {
var options = new LinuxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.png"),
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new LinuxPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else {
throw new PlatformNotSupportedException();
}
} finally {
File.WriteAllText(testStringFile, oldText);
}
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NuGet.Versioning;
using Velopack.Deployment;
using Velopack.Packaging.Exceptions;
using Velopack.Sources;
namespace Velopack.Packaging.Tests
{
public class S3DeploymentTests
{
public readonly static string B2_KEYID = "0035016844a4188000000000a";
public readonly static string B2_SECRET = Environment.GetEnvironmentVariable("VELOPACK_B2_TEST_TOKEN") ?? "K003jlDxnA1m3HAvNsyqzHIUmRuSdbE";
public readonly static string B2_BUCKET = "velopack-testing";
public readonly static string B2_ENDPOINT = "s3.eu-central-003.backblazeb2.com";
private readonly ITestOutputHelper _output;
public S3DeploymentTests(ITestOutputHelper output)
{
_output = output;
}
[SkippableFact]
public void CanDeployToBackBlazeB2()
{
Skip.If(String.IsNullOrWhiteSpace(B2_SECRET), "VELOPACK_B2_TEST_TOKEN is not set.");
using var logger = _output.BuildLoggerFor<S3DeploymentTests>();
using var _1 = Utility.GetTempDirectory(out var releaseDir);
string channel = String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI"))
? VelopackRuntimeInfo.SystemOs.GetOsShortName()
: "ci-" + VelopackRuntimeInfo.SystemOs.GetOsShortName();
// get latest version, and increment patch by one
var updateUrl = $"https://{B2_BUCKET}.{B2_ENDPOINT}/";
var source = new SimpleWebSource(updateUrl);
VelopackAssetFeed feed = new VelopackAssetFeed();
try {
feed = source.GetReleaseFeed(logger, channel).GetAwaiterResult();
} catch (Exception ex) {
logger.Warn(ex, "Failed to fetch release feed.");
}
var latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
.OrderByDescending(a => a.Version)
.FirstOrDefault();
var newVer = latest != null ? new SemanticVersion(1, 0, latest.Version.Patch + 1) : new SemanticVersion(1, 0, 0);
// create repo
var repo = new S3Repository(logger);
var options = new S3UploadOptions {
ReleaseDir = new DirectoryInfo(releaseDir),
Bucket = B2_BUCKET,
Channel = channel,
Endpoint = "https://" + B2_ENDPOINT,
KeyId = B2_KEYID,
Secret = B2_SECRET,
};
// download latest version and create delta
repo.DownloadLatestFullPackageAsync(options).GetAwaiterResult();
var id = "B2TestApp";
TestApp.PackTestApp(id, newVer.ToFullString(), $"b2-{DateTime.UtcNow.ToLongDateString()}", releaseDir, logger, channel: channel);
if (latest != null) {
// check delta was created
Assert.True(Directory.EnumerateFiles(releaseDir, "*-delta.nupkg").Any(), "No delta package was created.");
}
// upload new files
repo.UploadMissingAssetsAsync(options).GetAwaiterResult();
// verify that new version has been uploaded
feed = source.GetReleaseFeed(logger, channel).GetAwaiterResult();
latest = feed.Assets.Where(a => a.Version != null && a.Type == VelopackAssetType.Full)
.OrderByDescending(a => a.Version)
.FirstOrDefault();
Assert.True(latest != null, "No latest version found.");
Assert.Equal(newVer, latest.Version);
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;
namespace Velopack.Packaging.Tests
{
public static class TestApp
{
public static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
string releaseNotes = null, string channel = null)
{
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");
var oldText = File.ReadAllText(testStringFile);
try {
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
var psi = new ProcessStartInfo("dotnet");
psi.WorkingDirectory = projDir;
psi.AppendArgumentListSafe(args, out var debug);
logger.Info($"TEST: Running {psi.FileName} {debug}");
using var p = Process.Start(psi);
p.WaitForExit();
if (p.ExitCode != 0)
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
if (VelopackRuntimeInfo.IsWindows) {
var options = new WindowsPackOptions {
EntryExecutableName = "TestApp.exe",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new WindowsPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsOSX) {
var options = new OsxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.icns"),
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new OsxPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsLinux) {
var options = new LinuxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
Icon = Path.Combine(PathHelper.GetProjectDir(), "examples", "AvaloniaCrossPlat", "Velopack.png"),
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new LinuxPackCommandRunner(logger);
runner.Run(options).GetAwaiterResult();
} else {
throw new PlatformNotSupportedException();
}
} finally {
File.WriteAllText(testStringFile, oldText);
}
}
}
}