Fixed option to provide a connection string id for test methods

This commit is contained in:
Kenneth Skovhede
2026-02-05 10:43:16 +01:00
parent 9dd6dba0c2
commit fa6dd0fd94
5 changed files with 35 additions and 23 deletions
@@ -53,6 +53,10 @@ public sealed record DestinationTestRequestDto
/// The backup ID, if known
/// </summary>
public string? BackupId { get; init; }
/// <summary>
/// The connection string ID, if known
/// </summary>
public long? ConnectionStringId { get; init; }
/// <summary>
/// Any additional options to pass to the destination
@@ -67,9 +67,9 @@ public class SharedRemoteOperation
return modules;
}
public static async Task<(string Url, Dictionary<string, string?> Options)> ExpandUrl(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
public static async Task<(string Url, Dictionary<string, string?> Options)> ExpandUrl(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
url = UnmaskUrl(connection, url, backupId);
url = UnmaskUrl(connection, url, backupId, connectionStringId);
var uri = new Library.Utility.Uri(url);
var opts = ParseUrlOptions(connection, uri);
@@ -88,26 +88,26 @@ public class SharedRemoteOperation
return (url, opts);
}
public static async Task<BackendTupleDisposeWrapper> GetBackend(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
public static async Task<BackendTupleDisposeWrapper> GetBackend(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var backend = Library.DynamicLoader.BackendLoader.GetBackend(url, opts);
return new BackendTupleDisposeWrapper(backend, modules);
}
public static async Task<SourceProviderTupleDisposeWrapper> GetSourceProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
public static async Task<SourceProviderTupleDisposeWrapper> GetSourceProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var sourceProvider = await Library.DynamicLoader.SourceProviderLoader.GetSourceProviderForTesting(url, "", opts, cancelToken);
return new SourceProviderTupleDisposeWrapper(sourceProvider, modules);
}
public static async Task<RestoreDestinationProviderTupleDisposeWrapper> GetRestoreDestinationProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
public static async Task<RestoreDestinationProviderTupleDisposeWrapper> GetRestoreDestinationProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
(url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var restoreDestinationProvider = await Library.DynamicLoader.RestoreDestinationProviderLoader.GetRestoreDestinationProviderForTesting(url, opts, cancelToken);
@@ -132,9 +132,17 @@ public class SharedRemoteOperation
}
}
private static string UnmaskUrl(Connection connection, string maskedurl, string? backupId)
private static string UnmaskUrl(Connection connection, string maskedurl, string? backupId, long connectionStringId)
{
var previousUrl = !string.IsNullOrWhiteSpace(backupId) ? connection.GetBackup(backupId)?.TargetURL : null;
string? previousUrl = null;
if (!string.IsNullOrWhiteSpace(backupId))
previousUrl = connection.GetBackup(backupId)?.TargetURL;
if (string.IsNullOrWhiteSpace(previousUrl) && connectionStringId > 0)
previousUrl = connection.GetConnectionString(connectionStringId)?.BaseUrl;
var unmasked = string.IsNullOrWhiteSpace(previousUrl)
? maskedurl
: QuerystringMasking.Unmask(maskedurl, previousUrl);
@@ -31,7 +31,7 @@ namespace Duplicati.WebserverCore.Endpoints.V1
{
public class RemoteOperation : IEndpointV1
{
private record RemoteOperationInput(string path, string? backupId);
private record RemoteOperationInput(string path, string? backupId, long? connectionStringId);
public static void Map(RouteGroupBuilder group)
{
@@ -40,11 +40,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1
.RequireAuthorization();
group.MapPost("/remoteoperation/test", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromQuery] bool? autocreate, [FromQuery] Dto.V2.RemoteDestinationType? type, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
=> ExecuteTest(connection, applicationSettings, input.path, input.backupId, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken))
=> ExecuteTest(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken))
.RequireAuthorization();
group.MapPost("/remoteoperation/create", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
=> ExecuteCreate(connection, applicationSettings, input.path, input.backupId, cancelToken))
=> ExecuteCreate(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, cancelToken))
.RequireAuthorization();
}
@@ -54,25 +54,25 @@ namespace Duplicati.WebserverCore.Endpoints.V1
return new Dto.GetDbPathDto(!string.IsNullOrWhiteSpace(path), path);
}
private static async Task ExecuteTest(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken)
private static async Task ExecuteTest(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken)
{
try
{
if (type == Dto.V2.RemoteDestinationType.SourceProvider)
{
using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, maskedurl, backupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var s = wrapper.SourceProvider)
await s.Test(cancelToken).ConfigureAwait(false);
}
else if (type == Dto.V2.RemoteDestinationType.RestoreDestinationProvider)
{
using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, maskedurl, backupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var r = wrapper.RestoreDestinationProvider)
await r.Test(cancelToken).ConfigureAwait(false);
}
else
{
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
{
try { await b.TestAsync(cancelToken).ConfigureAwait(false); }
@@ -120,11 +120,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1
}
private static async Task ExecuteCreate(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, CancellationToken cancelToken)
private static async Task ExecuteCreate(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, CancellationToken cancelToken)
{
try
{
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
await b.CreateFolderAsync(cancelToken).ConfigureAwait(false);
}
@@ -55,7 +55,7 @@ public record WebModules : IEndpointV1
var url = options.GetValueOrDefault("url");
if (!string.IsNullOrWhiteSpace(url))
{
var (newUrl, opts) = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), cancellationToken);
var (newUrl, opts) = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), Library.Utility.Utility.ParseLongOption(options, "connection-string-id", -1), cancellationToken);
options["url"] = newUrl;
foreach (var k in opts.Keys)
options[k] = opts[k];
@@ -47,7 +47,7 @@ public class DestinationVerify : IEndpointV2
{
if (destinationType == RemoteDestinationType.SourceProvider)
{
using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
await wrapper.SourceProvider.Test(cancelToken);
// Technically we also count folders as files here, but really we just want to know if there is data to backup
var anyFiles = await wrapper.SourceProvider.Enumerate(cancelToken).AnyAsync(cancelToken);
@@ -60,7 +60,7 @@ public class DestinationVerify : IEndpointV2
}
else if (destinationType == RemoteDestinationType.RestoreDestinationProvider)
{
using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
await wrapper.RestoreDestinationProvider.Test(cancelToken);
return DestinationTestResponseDto.Create(
@@ -71,7 +71,7 @@ public class DestinationVerify : IEndpointV2
}
else
{
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
{