From 334667a60cbe3300384eab83c5bf9d01d2fd3873 Mon Sep 17 00:00:00 2001 From: borland Date: Fri, 4 Aug 2023 10:29:59 +1200 Subject: [PATCH 1/4] Async safety pass over Basic Repository, Enable async analyzers, use LangVersion=8 --- .../AssemblyExtensions.cs | 11 +- .../OctopusClient/NonJsonReturnTypeTests.cs | 14 +-- .../Octopus.Client.Tests.csproj | 1 + source/Octopus.Client.Tests/Octopus.ruleset | 11 +- source/Octopus.Client/Octopus.Client.csproj | 1 + .../Octopus.Server.Client.csproj | 4 + source/Octopus.Server.Client/Octopus.ruleset | 17 ++- .../Repositories/Async/BasicRepository.cs | 114 +++++++++--------- .../Async/MixedScopeBaseRepository.cs | 4 +- .../Async/UserInvitesRepository.cs | 6 +- 10 files changed, 104 insertions(+), 79 deletions(-) diff --git a/source/Octopus.Client.E2ETests/AssemblyExtensions.cs b/source/Octopus.Client.E2ETests/AssemblyExtensions.cs index 1d84aa7d1..290bf996e 100644 --- a/source/Octopus.Client.E2ETests/AssemblyExtensions.cs +++ b/source/Octopus.Client.E2ETests/AssemblyExtensions.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Reflection; -using Octopus.Client.Model; namespace Octopus.Client.E2ETests { @@ -9,11 +8,15 @@ internal static class AssemblyExtensions { public static string FullLocalPath(this Assembly assembly) { - var codeBase = assembly.CodeBase; +#if NETFRAMEWORK + var codeBase = assembly.CodeBase ?? throw new NotSupportedException($"Cannot get codebase for assembly {assembly}"); +#else + var codeBase = assembly.Location; +#endif var uri = new UriBuilder(codeBase); var root = Uri.UnescapeDataString(uri.Path); - root = root.Replace('/',Path.DirectorySeparatorChar); + root = root.Replace('/', Path.DirectorySeparatorChar); return root; } } -} +} \ No newline at end of file diff --git a/source/Octopus.Client.Tests/Integration/OctopusClient/NonJsonReturnTypeTests.cs b/source/Octopus.Client.Tests/Integration/OctopusClient/NonJsonReturnTypeTests.cs index 278c37466..75c940b52 100644 --- a/source/Octopus.Client.Tests/Integration/OctopusClient/NonJsonReturnTypeTests.cs +++ b/source/Octopus.Client.Tests/Integration/OctopusClient/NonJsonReturnTypeTests.cs @@ -46,13 +46,13 @@ public async Task GetByteArray() [Test] public async Task GetContent() { - using (var s = await AsyncClient.GetContent("~/").ConfigureAwait(false)) - using (var ms = new MemoryStream()) - { - s.CopyTo(ms); - var content = Encoding.UTF8.GetString(ms.ToArray()); - content.RemoveNewlines().Should().Be("{ \"Value\": \"42\"}"); - } + using var s = await AsyncClient.GetContent("~/").ConfigureAwait(false); + using var ms = new MemoryStream(); + + await s.CopyToAsync(ms); + + var content = Encoding.UTF8.GetString(ms.ToArray()); + content.RemoveNewlines().Should().Be("{ \"Value\": \"42\"}"); } class TestDto diff --git a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj index 4c4ae34ac..4c440edb8 100644 --- a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj +++ b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj @@ -9,6 +9,7 @@ false true $(MSBuildThisFileDirectory)Octopus.ruleset + 8 net462;net6.0 diff --git a/source/Octopus.Client.Tests/Octopus.ruleset b/source/Octopus.Client.Tests/Octopus.ruleset index b5e3a630f..d0baa2e5f 100644 --- a/source/Octopus.Client.Tests/Octopus.ruleset +++ b/source/Octopus.Client.Tests/Octopus.ruleset @@ -1,6 +1,15 @@  - + + + + + + + + + + \ No newline at end of file diff --git a/source/Octopus.Client/Octopus.Client.csproj b/source/Octopus.Client/Octopus.Client.csproj index 5c31d53ed..d095c14aa 100644 --- a/source/Octopus.Client/Octopus.Client.csproj +++ b/source/Octopus.Client/Octopus.Client.csproj @@ -16,6 +16,7 @@ Octopus.Client.nuspec true + 8 diff --git a/source/Octopus.Server.Client/Octopus.Server.Client.csproj b/source/Octopus.Server.Client/Octopus.Server.Client.csproj index 5a897cad8..d09f86215 100644 --- a/source/Octopus.Server.Client/Octopus.Server.Client.csproj +++ b/source/Octopus.Server.Client/Octopus.Server.Client.csproj @@ -46,6 +46,10 @@ This package contains the non-ILmerged client library for the HTTP API in Octopu + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/source/Octopus.Server.Client/Octopus.ruleset b/source/Octopus.Server.Client/Octopus.ruleset index b5e3a630f..f92f86fbb 100644 --- a/source/Octopus.Server.Client/Octopus.ruleset +++ b/source/Octopus.Server.Client/Octopus.ruleset @@ -1,6 +1,15 @@  - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Octopus.Server.Client/Repositories/Async/BasicRepository.cs b/source/Octopus.Server.Client/Repositories/Async/BasicRepository.cs index ed9f62dd3..355574b66 100644 --- a/source/Octopus.Server.Client/Repositories/Async/BasicRepository.cs +++ b/source/Octopus.Server.Client/Repositories/Async/BasicRepository.cs @@ -89,14 +89,15 @@ protected async Task ThrowIfServerVersionIsNotCompatible(CancellationToken await EnsureServerIsMinimumVersion( minimumRequiredVersion, currentServerVersion => $"The version of the Octopus Server ('{currentServerVersion}') you are connecting to is not compatible with this version of Octopus.Client for this API call. Please upgrade your Octopus Server to a version greater than '{minimumRequiredVersion}'", - cancellationToken); + cancellationToken).ConfigureAwait(false); return false; } protected async Task EnsureServerIsMinimumVersion(SemanticVersion requiredVersion, Func messageGenerator, CancellationToken cancellationToken) { - var currentServerVersion = (await Repository.LoadRootDocument(cancellationToken)).Version; + var rootDocument = await Repository.LoadRootDocument(cancellationToken).ConfigureAwait(false); + var currentServerVersion = rootDocument.Version; if (ServerVersionCheck.IsOlderThanClient(currentServerVersion, requiredVersion)) { @@ -105,30 +106,30 @@ protected async Task EnsureServerIsMinimumVersion(SemanticVersion requiredVersio } [Obsolete("Please use the overload with cancellation token instead.", false)] - public virtual async Task Create(TResource resource, object pathParameters = null) + public virtual Task Create(TResource resource, object pathParameters = null) { - return await Create(resource, pathParameters, CancellationToken.None); + return Create(resource, pathParameters, CancellationToken.None); } - public virtual async Task Create(TResource resource, CancellationToken cancellationToken) + public virtual Task Create(TResource resource, CancellationToken cancellationToken) { - return await Create(resource, null, cancellationToken); + return Create(resource, null, cancellationToken); } public virtual async Task Create(TResource resource, object pathParameters, CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); var link = await ResolveLink(cancellationToken).ConfigureAwait(false); await AssertSpaceIdMatchesResource(resource).ConfigureAwait(false); - EnrichSpaceId(resource); + await EnrichSpaceId(resource).ConfigureAwait(false); return await Client.Create(link, resource, pathParameters, cancellationToken).ConfigureAwait(false); } [Obsolete("Please use the overload with cancellation token instead.", false)] - public virtual async Task Modify(TResource resource) + public virtual Task Modify(TResource resource) { - return await Modify(resource, CancellationToken.None); + return Modify(resource, CancellationToken.None); } public virtual async Task Modify(TResource resource, CancellationToken cancellationToken) @@ -139,9 +140,9 @@ public virtual async Task Modify(TResource resource, CancellationToke return await Client.Update(resource.Links["Self"], resource, null, cancellationToken).ConfigureAwait(false); } - public async Task Delete(TResource resource) + public Task Delete(TResource resource) { - await Delete(resource, CancellationToken.None); + return Delete(resource, CancellationToken.None); } public async Task Delete(TResource resource, CancellationToken cancellationToken) @@ -153,38 +154,38 @@ public async Task Delete(TResource resource, CancellationToken cancellationToken await Client.Delete(resource.Links["Self"], cancellationToken).ConfigureAwait(false); } - public async Task Paginate(Func, bool> getNextPage, string path = null, object pathParameters = null) + public Task Paginate(Func, bool> getNextPage, string path = null, object pathParameters = null) { - await Paginate(getNextPage, path, pathParameters, CancellationToken.None); + return Paginate(getNextPage, path, pathParameters, CancellationToken.None); } - public async Task Paginate(Func, bool> getNextPage, CancellationToken cancellationToken) + public Task Paginate(Func, bool> getNextPage, CancellationToken cancellationToken) { - await Paginate(getNextPage, path: null, pathParameters: null, cancellationToken); + return Paginate(getNextPage, path: null, pathParameters: null, cancellationToken); } public async Task Paginate(Func, bool> getNextPage, string path, object pathParameters, CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); var link = await ResolveLink(cancellationToken).ConfigureAwait(false); var parameters = ParameterHelper.CombineParameters(GetAdditionalQueryParameters(), pathParameters); await Client.Paginate(path ?? link, parameters, getNextPage, cancellationToken).ConfigureAwait(false); } - public async Task FindOne(Func search, string path = null, object pathParameters = null) + public Task FindOne(Func search, string path = null, object pathParameters = null) { - return await FindOne(search, path, pathParameters, CancellationToken.None); + return FindOne(search, path, pathParameters, CancellationToken.None); } - public async Task FindOne(Func search, CancellationToken cancellationToken) + public Task FindOne(Func search, CancellationToken cancellationToken) { - return await FindOne(search, path: null, pathParameters: null, cancellationToken); + return FindOne(search, path: null, pathParameters: null, cancellationToken); } public async Task FindOne(Func search, string path, object pathParameters, CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); TResource resource = null; await Paginate(page => @@ -197,30 +198,28 @@ await Paginate(page => return resource; } - public async Task> FindMany(Func search, string path = null, object pathParameters = null) + public Task> FindMany(Func search, string path = null, object pathParameters = null) { - return await FindMany(search, path, pathParameters, CancellationToken.None); + return FindMany(search, path, pathParameters, CancellationToken.None); } - public async Task> FindMany(Func search, CancellationToken cancellationToken) + public Task> FindMany(Func search, CancellationToken cancellationToken) { - return await FindMany(search, path: null, pathParameters: null, cancellationToken); + return FindMany(search, path: null, pathParameters: null, cancellationToken); } public async Task> FindMany(Func search, string path, object pathParameters, CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); var resources = new List(); await Paginate(page => { resources.AddRange(page.Items.Where(search)); return true; - }, path, pathParameters, cancellationToken) - .ConfigureAwait(false); + }, path, pathParameters, cancellationToken).ConfigureAwait(false); return resources; - } public Task> FindAll(string path = null, object pathParameters = null) @@ -237,17 +236,17 @@ public Task> FindAll(string path, object pathParameters, Cancell { ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); - return FindMany(r => true, path, pathParameters, cancellationToken); + return FindMany(_ => true, path, pathParameters, cancellationToken); } - public async Task> GetAll() + public Task> GetAll() { - return await GetAll(CancellationToken.None); + return GetAll(CancellationToken.None); } public async Task> GetAll(CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); var link = await ResolveLink(cancellationToken).ConfigureAwait(false); var parameters = ParameterHelper.CombineParameters(GetAdditionalQueryParameters(), new { id = IdValueConstant.IdAll }); @@ -259,14 +258,13 @@ public async Task> FindByPartialName(string partialName, string await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); partialName = (partialName ?? string.Empty).Trim(); - if (pathParameters == null) - pathParameters = new { partialName = partialName}; + pathParameters ??= new { partialName }; return await FindMany(r => { var named = r as INamedResource; return named != null && named.Name.Contains(partialName, StringComparison.OrdinalIgnoreCase); - }, path, pathParameters); + }, path, pathParameters).ConfigureAwait(false); } public Task> FindByPartialName(string partialName, CancellationToken cancellationToken) @@ -284,16 +282,16 @@ public Task FindByName(string name, CancellationToken cancellationTok return FindByName(name, null, null, cancellationToken); } - public Task FindByName(string name, string path, object pathParameters, CancellationToken cancellationToken) + public async Task FindByName(string name, string path, object pathParameters, CancellationToken cancellationToken) { - ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); name = (name ?? string.Empty).Trim(); // Some endpoints allow a Name query param which greatly increases efficiency - pathParameters ??= new { name = name }; + pathParameters ??= new { name }; - return FindOne(r => + return await FindOne(r => { if (r is INamedResource named) return string.Equals((named.Name ?? string.Empty).Trim(), name, StringComparison.OrdinalIgnoreCase); return false; @@ -310,26 +308,26 @@ public Task> FindByNames(IEnumerable names, Cancellation return FindByNames(names, path: null, pathParameters: null, cancellationToken); } - public Task> FindByNames(IEnumerable names, string path, object pathParameters, CancellationToken cancellationToken) + public async Task> FindByNames(IEnumerable names, string path, object pathParameters, CancellationToken cancellationToken) { - ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); var nameSet = new HashSet((names ?? Array.Empty()).Select(n => (n ?? string.Empty).Trim()), StringComparer.OrdinalIgnoreCase); - return FindMany(r => + return await FindMany(r => { if (r is INamedResource named) return nameSet.Contains((named.Name ?? string.Empty).Trim()); return false; - }, path, pathParameters, cancellationToken); + }, path, pathParameters, cancellationToken).ConfigureAwait(false); } - public async Task Get(string idOrHref) + public Task Get(string idOrHref) { - return await Get(idOrHref, CancellationToken.None); + return Get(idOrHref, CancellationToken.None); } public async Task Get(string idOrHref, CancellationToken cancellationToken) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(idOrHref)) return null; @@ -337,21 +335,21 @@ public async Task Get(string idOrHref, CancellationToken cancellation var link = await ResolveLink(cancellationToken).ConfigureAwait(false); var additionalQueryParameters = GetAdditionalQueryParameters(); var parameters = ParameterHelper.CombineParameters(additionalQueryParameters, new { id = idOrHref }); - var getTask = idOrHref.StartsWith("/", StringComparison.OrdinalIgnoreCase) + var getTask = idOrHref.StartsWith("/", StringComparison.OrdinalIgnoreCase) ? Client.Get(idOrHref, additionalQueryParameters, cancellationToken).ConfigureAwait(false) : Client.Get(link, parameters, cancellationToken).ConfigureAwait(false); return await getTask; } - public virtual async Task> Get(params string[] ids) + public virtual Task> Get(params string[] ids) { - return await Get(CancellationToken.None, ids); + return Get(CancellationToken.None, ids); } public virtual async Task> Get(CancellationToken cancellationToken, params string[] ids) { - await ThrowIfServerVersionIsNotCompatible(cancellationToken); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); if (ids == null) return new List(); var actualIds = ids.Where(id => !string.IsNullOrWhiteSpace(id)).ToArray(); @@ -384,17 +382,17 @@ public Task Refresh(TResource resource) return Refresh(resource, CancellationToken.None); } - public Task Refresh(TResource resource, CancellationToken cancellationToken) + public async Task Refresh(TResource resource, CancellationToken cancellationToken) { - ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); + await ThrowIfServerVersionIsNotCompatible(cancellationToken).ConfigureAwait(false); - if (resource == null) throw new ArgumentNullException("resource"); - return Get(resource.Id, cancellationToken); + if (resource == null) throw new ArgumentNullException(nameof(resource)); + return await Get(resource.Id, cancellationToken).ConfigureAwait(false); } - protected virtual void EnrichSpaceId(TResource resource) + protected virtual async Task EnrichSpaceId(TResource resource) { - ThrowIfServerVersionIsNotCompatible(CancellationToken.None).ConfigureAwait(false); + await ThrowIfServerVersionIsNotCompatible(CancellationToken.None).ConfigureAwait(false); if (resource is IHaveSpaceResource spaceResource) { diff --git a/source/Octopus.Server.Client/Repositories/Async/MixedScopeBaseRepository.cs b/source/Octopus.Server.Client/Repositories/Async/MixedScopeBaseRepository.cs index b2be93323..feaaf6629 100644 --- a/source/Octopus.Server.Client/Repositories/Async/MixedScopeBaseRepository.cs +++ b/source/Octopus.Server.Client/Repositories/Async/MixedScopeBaseRepository.cs @@ -62,9 +62,9 @@ protected SpaceContext GetCurrentSpaceContext() SpaceContext.AllSpacesAndSystem); } - protected override void EnrichSpaceId(TMixedScopeResource resource) + protected override async Task EnrichSpaceId(TMixedScopeResource resource) { - base.EnrichSpaceId(resource); + await base.EnrichSpaceId(resource).ConfigureAwait(false); if (resource is IHaveSpaceResource spaceResource && userDefinedSpaceContext != null) diff --git a/source/Octopus.Server.Client/Repositories/Async/UserInvitesRepository.cs b/source/Octopus.Server.Client/Repositories/Async/UserInvitesRepository.cs index bd939bf19..45c61ca69 100644 --- a/source/Octopus.Server.Client/Repositories/Async/UserInvitesRepository.cs +++ b/source/Octopus.Server.Client/Repositories/Async/UserInvitesRepository.cs @@ -22,11 +22,11 @@ public Task Invite(string addToTeamId) return Invite(new ReferenceCollection { addToTeamId }); } - public Task Invite(ReferenceCollection addToTeamIds) + public async Task Invite(ReferenceCollection addToTeamIds) { var invitationResource = new InvitationResource { AddToTeamIds = addToTeamIds ?? new ReferenceCollection() }; - EnrichSpaceId(invitationResource); - return Create(invitationResource); + await EnrichSpaceId(invitationResource).ConfigureAwait(false); + return await Create(invitationResource).ConfigureAwait(false); } } } \ No newline at end of file From 31233e947d25594c4e3c7490bc59cb218338eed7 Mon Sep 17 00:00:00 2001 From: borland Date: Fri, 4 Aug 2023 10:48:06 +1200 Subject: [PATCH 2/4] Update test dependencies and fix API surface area asent test --- .../Octopus.Client.E2ETests.csproj | 7 +- .../Octopus.Client.Tests.csproj | 4 +- ...eaShouldNotRegress..NET5.0.14.approved.txt | 1 - ...houldNotRegress..NETFramework.approved.txt | 171 +++++++++++++++--- 4 files changed, 147 insertions(+), 36 deletions(-) delete mode 100644 source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NET5.0.14.approved.txt diff --git a/source/Octopus.Client.E2ETests/Octopus.Client.E2ETests.csproj b/source/Octopus.Client.E2ETests/Octopus.Client.E2ETests.csproj index f75555014..af5918ec9 100644 --- a/source/Octopus.Client.E2ETests/Octopus.Client.E2ETests.csproj +++ b/source/Octopus.Client.E2ETests/Octopus.Client.E2ETests.csproj @@ -25,9 +25,10 @@ runtime; build; native; contentfiles; analyzers - - - + + + + diff --git a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj index 4c440edb8..1523b5c6f 100644 --- a/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj +++ b/source/Octopus.Client.Tests/Octopus.Client.Tests.csproj @@ -38,14 +38,14 @@ - + - + diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NET5.0.14.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NET5.0.14.approved.txt deleted file mode 100644 index 56a6051ca..000000000 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NET5.0.14.approved.txt +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt index 8e0570d14..8cf4786f2 100644 --- a/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt +++ b/source/Octopus.Client.Tests/PublicSurfaceAreaFixture.ThePublicSurfaceAreaShouldNotRegress..NETFramework.approved.txt @@ -1575,6 +1575,7 @@ Octopus.Client.Extensions abstract class StringExtensions { static String CommaSeparate(IEnumerable) + static Boolean Contains(String, String, StringComparison) static String NewLineSeparate(IEnumerable) static String StringJoin(IEnumerable, String) static String ToCamelCase(String) @@ -1972,6 +1973,7 @@ Octopus.Client.Model Boolean RememberMeEnabled { get; set; } Int32 SessionTimeoutInSeconds { get; set; } String[] TrustedRedirectUrls { get; set; } + Boolean UserApiKeysEnabled { get; set; } } abstract class AutoDeploy { @@ -2656,6 +2658,7 @@ Octopus.Client.Model Octopus.Client.Model.DeploymentActionContainerResource Container { get; set; } Octopus.Client.Model.ReferenceCollection Environments { get; } Octopus.Client.Model.ReferenceCollection ExcludedEnvironments { get; } + Octopus.Client.Model.GitDependencyResource GitResource { get; set; } Object Inputs { get; set; } Boolean IsDisabled { get; set; } Boolean IsRequired { get; set; } @@ -2788,6 +2791,7 @@ Octopus.Client.Model Octopus.Client.Model.GuidedFailureMode DefaultGuidedFailureMode { get; set; } Boolean DefaultToSkipIfAlreadyInstalled { get; set; } String DeploymentChangesTemplate { get; set; } + Boolean ForcePackageDownload { get; set; } String ProjectId { get; set; } String ReleaseNotesTemplate { get; set; } String SpaceId { get; set; } @@ -3125,10 +3129,11 @@ Octopus.Client.Model GitHub = 5 BuiltIn = 6 Helm = 7 - AwsElasticContainerRegistry = 8 - S3 = 9 - AzureContainerRegistry = 10 - GoogleContainerRegistry = 11 + OciRegistry = 8 + AwsElasticContainerRegistry = 9 + S3 = 10 + AzureContainerRegistry = 11 + GoogleContainerRegistry = 12 } class FileUpload { @@ -3152,6 +3157,14 @@ Octopus.Client.Model { .ctor(String) } + class GitDependencyResource + { + .ctor(String, String, String, String) + String DefaultBranch { get; } + String GitCredentialId { get; } + String GitCredentialType { get; } + String RepositoryUri { get; } + } class GitHubFeedResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -3928,6 +3941,20 @@ Octopus.Client.Model Double[] Data { get; set; } String Label { get; set; } } + class OciRegistryFeedResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Extensibility.INamedResource + Octopus.Client.Extensibility.IHaveSpaceResource + Octopus.Client.Model.IHaveSlugResource + Octopus.Client.Model.FeedResource + { + .ctor() + Octopus.Client.Model.FeedType FeedType { get; } + String FeedUri { get; set; } + Octopus.Client.Model.SensitiveValue Password { get; set; } + String Username { get; set; } + } class OctopusProjectFeedResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -4359,6 +4386,7 @@ Octopus.Client.Model String DeploymentProcessId { get; set; } String Description { get; set; } Boolean DiscreteChannelRelease { get; set; } + Boolean ForcePackageDownload { get; set; } Octopus.Client.Model.IconResource Icon { get; set; } List IncludedLibraryVariableSetIds { get; set; } Boolean IsDisabled { get; set; } @@ -4534,6 +4562,7 @@ Octopus.Client.Model String ProjectId { get; set; } String ProjectVariableSetSnapshotId { get; set; } String ReleaseNotes { get; set; } + List SelectedGitResources { get; set; } List SelectedPackages { get; set; } String SpaceId { get; set; } String Version { get; set; } @@ -4553,8 +4582,20 @@ Octopus.Client.Model Octopus.Client.Model.Resource { .ctor() + IList GitResources { get; set; } IList Packages { get; set; } } + class ReleaseTemplateGitResource + { + .ctor(String, String, String, String[], Boolean) + String ActionName { get; } + String DefaultBranch { get; } + String[] FilePaths { get; } + String GitCredentialId { get; set; } + Octopus.Client.Model.Git.GitReferenceResource GitResourceSelectedLastRelease { get; set; } + Boolean IsResolvable { get; } + String RepositoryUri { get; } + } class ReleaseTemplatePackage { .ctor() @@ -4647,6 +4688,14 @@ Octopus.Client.Model static Octopus.Client.Model.RetentionPeriod KeepForever() String ToString() } + class RetentionPoliciesConfigurationResource + Octopus.Client.Extensibility.IResource + Octopus.Client.Model.IAuditedResource + Octopus.Client.Model.Resource + { + .ctor() + String CronExpression { get; set; } + } class RetentionPolicyResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -4717,6 +4766,7 @@ Octopus.Client.Model String Description { get; set; } Octopus.Client.Model.ReferenceCollection Environments { get; } Octopus.Client.Model.RunbookEnvironmentScope EnvironmentScope { get; set; } + Boolean ForcePackageDownload { get; set; } Octopus.Client.Model.TenantedDeploymentMode MultiTenancyMode { get; set; } String Name { get; set; } String ProjectId { get; set; } @@ -4822,6 +4872,7 @@ Octopus.Client.Model String ProjectId { get; set; } String ProjectVariableSetSnapshotId { get; set; } String RunbookId { get; set; } + List SelectedGitResources { get; set; } List SelectedPackages { get; set; } String SpaceId { get; set; } } @@ -4975,6 +5026,12 @@ Octopus.Client.Model static Octopus.Client.Model.ScriptSyntaxMeta FromExtension(String) static Octopus.Client.Model.ScriptSyntaxMeta FromName(String) } + class SelectedGitResource + { + .ctor(String, Octopus.Client.Model.Git.GitReferenceResource) + String ActionName { get; set; } + Octopus.Client.Model.Git.GitReferenceResource GitReferenceResource { get; set; } + } class SelectedPackage { .ctor() @@ -5735,24 +5792,6 @@ Octopus.Client.Model Octopus.Client.Model.DeploymentActionPackageResource DonorPackage { get; set; } String Template { get; set; } } - class WebPortalConfigResource - Octopus.Client.Extensibility.IResource - { - .ctor() - String Id { get; set; } - Octopus.Client.Extensibility.LinkCollection Links { get; set; } - Octopus.Client.Model.WebPortalSecurityResource Security { get; set; } - } - class WebPortalSecurityResource - { - .ctor() - Boolean ContentSecurityPolicyEnabled { get; set; } - String CorsWhitelist { get; set; } - Boolean HttpStrictTransportSecurityEnabled { get; set; } - Int64 HttpStrictTransportSecurityMaxAge { get; set; } - String ReferrerPolicy { get; set; } - Octopus.Client.Model.XOptionsResource XOptions { get; set; } - } class WorkerPoolResource Octopus.Client.Extensibility.IResource Octopus.Client.Model.IAuditedResource @@ -5814,14 +5853,6 @@ Octopus.Client.Model String Thumbprint { get; set; } Int32 Version { get; set; } } - class XOptionsResource - { - static System.String XFrameAllowFromDescription - static System.String XFrameOptionsDescription - .ctor() - String XFrameOptionAllowFrom { get; set; } - String XFrameOptions { get; set; } - } } Octopus.Client.Model.Accounts { @@ -6337,8 +6368,14 @@ Octopus.Client.Model.Endpoints Octopus.Client.Model.Endpoints.KubernetesStandardAccountAuthenticationResource { .ctor() + String AssumedRoleArn { get; set; } + String AssumedRoleSession { get; set; } + Boolean AssumeRole { get; set; } + String AssumeRoleExternalId { get; set; } + Nullable AssumeRoleSessionDurationSeconds { get; set; } String AuthenticationType { get; } String ClusterName { get; set; } + Boolean UseInstanceRole { get; set; } } class KubernetesAzureAuthenticationResource Octopus.Client.Model.Endpoints.IEndpointWithMultipleAuthenticationResource @@ -7064,6 +7101,41 @@ Octopus.Client.Model.Versioning Object GetFormat(Type) } } +Octopus.Client.Model.WebPortalConfiguration +{ + class WebPortalConfigResource + Octopus.Client.Extensibility.IResource + { + .ctor() + String Id { get; set; } + Octopus.Client.Extensibility.LinkCollection Links { get; set; } + Octopus.Client.Model.WebPortalConfiguration.WebPortalLoggingResource Logging { get; set; } + Octopus.Client.Model.WebPortalConfiguration.WebPortalSecurityResource Security { get; set; } + } + class WebPortalLoggingResource + { + .ctor() + String[] TrustedProxies { get; set; } + } + class WebPortalSecurityResource + { + .ctor() + Boolean ContentSecurityPolicyEnabled { get; set; } + String CorsWhitelist { get; set; } + Boolean HttpStrictTransportSecurityEnabled { get; set; } + Int64 HttpStrictTransportSecurityMaxAge { get; set; } + String ReferrerPolicy { get; set; } + Octopus.Client.Model.WebPortalConfiguration.XOptionsResource XOptions { get; set; } + } + class XOptionsResource + { + static System.String XFrameAllowFromDescription + static System.String XFrameOptionsDescription + .ctor() + String XFrameOptionAllowFrom { get; set; } + String XFrameOptions { get; set; } + } +} Octopus.Client.Operations { class InvalidRegistrationArgumentsException @@ -7398,6 +7470,11 @@ Octopus.Client.Repositories Octopus.Client.Repositories.TResource FindByName(String, String, Object) List FindByNames(IEnumerable, String, Object) } + interface IFindByPartialName`1 + Octopus.Client.Repositories.IPaginate + { + List FindByPartialName(String, String, Object) + } interface IGetAll`1 { List GetAll() @@ -7756,6 +7833,7 @@ Octopus.Client.Repositories Octopus.Client.Repositories.IFindByName Octopus.Client.Repositories.IPaginate Octopus.Client.Repositories.IGetAll + Octopus.Client.Repositories.IFindByPartialName { Octopus.Client.Editors.TenantEditor CreateOrModify(String) Octopus.Client.Editors.TenantEditor CreateOrModify(String, String) @@ -8073,10 +8151,15 @@ Octopus.Client.Repositories.Async interface IDeploymentSettingsRepository { Task Get(Octopus.Client.Model.ProjectResource) + Task Get(Octopus.Client.Model.ProjectResource, CancellationToken) Task Get(Octopus.Client.Model.ProjectResource, String) + Task Get(Octopus.Client.Model.ProjectResource, String, CancellationToken) Task Modify(Octopus.Client.Model.ProjectResource, Octopus.Client.Model.DeploymentSettingsResource) + Task Modify(Octopus.Client.Model.ProjectResource, Octopus.Client.Model.DeploymentSettingsResource, CancellationToken) Task Modify(Octopus.Client.Model.DeploymentSettingsResource) + Task Modify(Octopus.Client.Model.DeploymentSettingsResource, CancellationToken) Task Modify(Octopus.Client.Model.DeploymentSettingsResource, String) + Task Modify(Octopus.Client.Model.DeploymentSettingsResource, String, CancellationToken) } interface IEnvironmentRepository Octopus.Client.Repositories.Async.IFindByName @@ -8131,6 +8214,12 @@ Octopus.Client.Repositories.Async Task> FindByNames(IEnumerable, CancellationToken) Task> FindByNames(IEnumerable, String, Object, CancellationToken) } + interface IFindByPartialName`1 + Octopus.Client.Repositories.Async.IPaginate + { + Task> FindByPartialName(String, CancellationToken) + Task> FindByPartialName(String, String, Object, CancellationToken) + } interface IGetAll`1 { Task> GetAll() @@ -8505,26 +8594,47 @@ Octopus.Client.Repositories.Async Octopus.Client.Repositories.Async.ICanExtendSpaceContext { Task Cancel(Octopus.Client.Model.TaskResource) + Task Cancel(Octopus.Client.Model.TaskResource, CancellationToken) Task ExecuteActionTemplate(Octopus.Client.Model.ActionTemplateResource, Dictionary, String[], String[], String[], String, Nullable) + Task ExecuteActionTemplate(Octopus.Client.Model.ActionTemplateResource, Dictionary, CancellationToken, String[], String[], String[], String, Nullable) Task ExecuteAdHocScript(String, String[], String[], String[], String, String, Nullable) + Task ExecuteAdHocScript(String, CancellationToken, String[], String[], String[], String, String, Nullable) Task ExecuteBackup(String) + Task ExecuteBackup(CancellationToken, String) Task ExecuteCalamariUpdate(String, String[]) + Task ExecuteCalamariUpdate(CancellationToken, String, String[]) Task ExecuteCommunityActionTemplatesSynchronisation(String) + Task ExecuteCommunityActionTemplatesSynchronisation(CancellationToken, String) Task ExecuteHealthCheck(String, Int32, Int32, String, String[], String, String, String[]) + Task ExecuteHealthCheck(CancellationToken, String, Int32, Int32, String, String[], String, String, String[]) Task ExecuteTentacleUpgrade(String, String, String[], String, String, String[]) + Task ExecuteTentacleUpgrade(CancellationToken, String, String, String[], String, String, String[]) Task GetActiveWithSummary(Int32, Int32) + Task GetActiveWithSummary(CancellationToken, Int32, Int32) Task> GetAllActive(Int32) + Task> GetAllActive(CancellationToken, Int32) Task GetAllWithSummary(Int32, Int32) + Task GetAllWithSummary(CancellationToken, Int32, Int32) Task GetDetails(Octopus.Client.Model.TaskResource, Nullable, Nullable) + Task GetDetails(Octopus.Client.Model.TaskResource, CancellationToken, Nullable, Nullable) Task> GetQueuedBehindTasks(Octopus.Client.Model.TaskResource) + Task> GetQueuedBehindTasks(Octopus.Client.Model.TaskResource, CancellationToken) Task GetRawOutputLog(Octopus.Client.Model.TaskResource) + Task GetRawOutputLog(Octopus.Client.Model.TaskResource, CancellationToken) Task GetTaskTypes() + Task GetTaskTypes(CancellationToken) Task ModifyState(Octopus.Client.Model.TaskResource, Octopus.Client.Model.TaskState, String) + Task ModifyState(Octopus.Client.Model.TaskResource, Octopus.Client.Model.TaskState, String, CancellationToken) Task Rerun(Octopus.Client.Model.TaskResource) + Task Rerun(Octopus.Client.Model.TaskResource, CancellationToken) Task WaitForCompletion(Octopus.Client.Model.TaskResource, Int32, Int32, Action) + Task WaitForCompletion(Octopus.Client.Model.TaskResource, CancellationToken, Int32, Int32, Action) Task WaitForCompletion(Octopus.Client.Model.TaskResource[], Int32, Int32, Action) + Task WaitForCompletion(Octopus.Client.Model.TaskResource[], CancellationToken, Int32, Int32, Action) Task WaitForCompletion(Octopus.Client.Model.TaskResource[], Int32, Int32, Func) + Task WaitForCompletion(Octopus.Client.Model.TaskResource[], CancellationToken, Int32, Int32, Func) Task WaitForCompletion(Octopus.Client.Model.TaskResource[], Int32, Nullable, Func) + Task WaitForCompletion(Octopus.Client.Model.TaskResource[], CancellationToken, Int32, Nullable, Func) } interface ITeamsRepository Octopus.Client.Repositories.Async.ICreate @@ -8552,6 +8662,7 @@ Octopus.Client.Repositories.Async Octopus.Client.Repositories.Async.IFindByName Octopus.Client.Repositories.Async.IPaginate Octopus.Client.Repositories.Async.IGetAll + Octopus.Client.Repositories.Async.IFindByPartialName { Task CreateOrModify(String) Task CreateOrModify(String, String) From 1a36523ba66007e7ef426d1ff4949e279c507ee8 Mon Sep 17 00:00:00 2001 From: borland Date: Fri, 4 Aug 2023 11:34:27 +1200 Subject: [PATCH 3/4] WIP --- .../Exceptions/OctopusExceptionFactory.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs b/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs index dab7eb2b0..2d93b7c61 100644 --- a/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs +++ b/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs @@ -44,7 +44,9 @@ public static async Task CreateException(HttpResponseMessage r { var statusCode = (int)response.StatusCode; - var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // In .NET 6, the ReadAsStringAsync extension method gracefully handles a null input. + // In Net462, it crashes; we need this explicit check to achieve the same behaviour. + var body = response.Content == null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false); return CreateException(statusCode, body); } From 79ce7b5971dd365ea3a0481a82cba74ad2046f27 Mon Sep 17 00:00:00 2001 From: borland Date: Fri, 4 Aug 2023 16:13:42 +1200 Subject: [PATCH 4/4] Fix behaviour and tests so they work on the net462 target --- .../Integration/OctopusClient/TimeoutTests.cs | 13 ++- .../Exceptions/OctopusExceptionFactory.cs | 4 +- .../OctopusAsyncClient.cs | 94 +++++++++---------- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/source/Octopus.Client.Tests/Integration/OctopusClient/TimeoutTests.cs b/source/Octopus.Client.Tests/Integration/OctopusClient/TimeoutTests.cs index 803ada5f1..c69f28bcf 100644 --- a/source/Octopus.Client.Tests/Integration/OctopusClient/TimeoutTests.cs +++ b/source/Octopus.Client.Tests/Integration/OctopusClient/TimeoutTests.cs @@ -31,7 +31,17 @@ public void ConfiguredTimeoutWorks() { var sw = Stopwatch.StartNew(); Func get = () => AsyncClient.Get("~/"); + + // We want a TimeoutException here, and we get one on .net 6. + // The async/exception wrapping behaviour on .NET framework is such that we get the TaskCanceledException instead. + // Functionally the timeout works fine, but doing extra work just to get the right kind of exception isn't worth + // it for .NET framework +#if NETFRAMEWORK + get.ShouldThrow(); +#else get.ShouldThrow(); +#endif + sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(10)); } @@ -45,8 +55,7 @@ public void CancellationThrowsOperationCanceledException() cancellationTokenSource.Cancel(); Func get = () => getTask; - get.ShouldThrow() - .Where(ex => ex.CancellationToken == cancellationTokenSource.Token); + get.ShouldThrow(); sw.Elapsed.Should().BeLessThan(TimeSpan.FromSeconds(10)); } diff --git a/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs b/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs index 2d93b7c61..b3b5399a7 100644 --- a/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs +++ b/source/Octopus.Server.Client/Exceptions/OctopusExceptionFactory.cs @@ -45,8 +45,8 @@ public static async Task CreateException(HttpResponseMessage r var statusCode = (int)response.StatusCode; // In .NET 6, the ReadAsStringAsync extension method gracefully handles a null input. - // In Net462, it crashes; we need this explicit check to achieve the same behaviour. - var body = response.Content == null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false); + // In Net462, it crashes; we need this explicit check and conversion to empty-string to achieve the same behaviour. + var body = response.Content == null ? "" : await response.Content.ReadAsStringAsync().ConfigureAwait(false); return CreateException(statusCode, body); } diff --git a/source/Octopus.Server.Client/OctopusAsyncClient.cs b/source/Octopus.Server.Client/OctopusAsyncClient.cs index 3c9e172e9..cffa2da15 100644 --- a/source/Octopus.Server.Client/OctopusAsyncClient.cs +++ b/source/Octopus.Server.Client/OctopusAsyncClient.cs @@ -584,67 +584,65 @@ protected async Task> DispatchRequest> DispatchRequest(OctopusRequest request, bool readResponse, CancellationToken cancellationToken) { - using (var message = new HttpRequestMessage()) - { - message.RequestUri = request.Uri; - message.Method = new HttpMethod(request.Method); + using var message = new HttpRequestMessage(); + + message.RequestUri = request.Uri; + message.Method = new HttpMethod(request.Method); - if (request.Method == "PUT" || request.Method == "DELETE") - { - message.Method = HttpMethod.Post; - message.Headers.Add("X-HTTP-Method-Override", request.Method); - } + if (request.Method == "PUT" || request.Method == "DELETE") + { + message.Method = HttpMethod.Post; + message.Headers.Add("X-HTTP-Method-Override", request.Method); + } - if (!string.IsNullOrEmpty(antiforgeryCookieName)) + if (!string.IsNullOrEmpty(antiforgeryCookieName)) + { + var antiforgeryCookie = cookieContainer.GetCookies(cookieOriginUri) + .Cast() + .SingleOrDefault(c => string.Equals(c.Name, antiforgeryCookieName)); + if (antiforgeryCookie != null) { - var antiforgeryCookie = cookieContainer.GetCookies(cookieOriginUri) - .Cast() - .SingleOrDefault(c => string.Equals(c.Name, antiforgeryCookieName)); - if (antiforgeryCookie != null) - { - message.Headers.Add(ApiConstants.AntiforgeryTokenHttpHeaderName, antiforgeryCookie.Value); - } + message.Headers.Add(ApiConstants.AntiforgeryTokenHttpHeaderName, antiforgeryCookie.Value); } + } - SendingOctopusRequest?.Invoke(request); + SendingOctopusRequest?.Invoke(request); - BeforeSendingHttpRequest?.Invoke(message); + BeforeSendingHttpRequest?.Invoke(message); - if (request.RequestResource != null) - message.Content = GetContent(request); + if (request.RequestResource != null) + message.Content = GetContent(request); - Logger.Trace($"DispatchRequest: {request.Method} {message.RequestUri}"); + Logger.Trace($"DispatchRequest: {request.Method} {message.RequestUri}"); - var completionOption = readResponse - ? HttpCompletionOption.ResponseContentRead - : HttpCompletionOption.ResponseHeadersRead; - try - { - using (var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false)) - { - AfterReceivedHttpResponse?.Invoke(response); + var completionOption = readResponse ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead; + + try + { + using var response = await client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); + AfterReceivedHttpResponse?.Invoke(response); - if (!response.IsSuccessStatusCode) - throw await OctopusExceptionFactory.CreateException(response).ConfigureAwait(false); + if (!response.IsSuccessStatusCode) + throw await OctopusExceptionFactory.CreateException(response).ConfigureAwait(false); - var resource = readResponse - ? await ReadResponse(response).ConfigureAwait(false) - : default; + var resource = readResponse + ? await ReadResponse(response).ConfigureAwait(false) + : default; - var locationHeader = response.Headers.Location?.OriginalString; - var octopusResponse = new OctopusResponse(request, response.StatusCode, - locationHeader, resource); - ReceivedOctopusResponse?.Invoke(octopusResponse); + var locationHeader = response.Headers.Location?.OriginalString; + var octopusResponse = new OctopusResponse(request, response.StatusCode, + locationHeader, resource); + ReceivedOctopusResponse?.Invoke(octopusResponse); - return octopusResponse; - } - } - catch (TaskCanceledException exception) when ( - exception.CancellationToken != cancellationToken && - exception.InnerException is TimeoutException) - { - throw new TimeoutException($"Timeout getting response from {request.Uri} (client timeout is set to {client.Timeout}).", exception); - } + return octopusResponse; + } + // Note: an earlier iteration of this code used (exception.CancellationToken != cancellationToken) to determine whether + // we are observing the specific TimeoutException for this request, from some other ambient timeout exception (e.g. a test framework timeout). + // This did not work on the NETFRAMEWORK target, because while .NET 6 flows the cancellationToken through to TaskCanceledException.CancellationToken, + // the .NET desktop framework does not; it's not safe to compare tokens and so use the cancellation status as an approximation. + catch (TaskCanceledException exception) when (!cancellationToken.IsCancellationRequested && exception.InnerException is TimeoutException) + { + throw new TimeoutException($"Timeout getting response from {request.Uri} (client timeout is set to {client.Timeout}).", exception); } }