From 39d7f77777c18c27ea05d17229d04497c7f454e0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 30 Mar 2025 16:30:58 -0700 Subject: [PATCH 1/3] Link resources in more cases - Handle more methods - Handle more expressions - Added tests --- .../AzureBicepResourceExtensions.cs | 17 ++++ .../ConnectionStringBuilderExtensions.cs | 1 + .../ResourceBuilderExtensions.cs | 99 ++++++++++++++++++- .../AzureBicepProvisionerTests.cs | 20 ++++ .../ContainerResourceTests.cs | 4 + .../ExecutableResourceTests.cs | 10 ++ .../ProjectResourceTests.cs | 3 + .../WithEnvironmentTests.cs | 37 +++++++ .../WithReferenceTests.cs | 47 +++++++++ 9 files changed, 236 insertions(+), 2 deletions(-) diff --git a/src/Aspire.Hosting.Azure/AzureBicepResourceExtensions.cs b/src/Aspire.Hosting.Azure/AzureBicepResourceExtensions.cs index e112f6108bb..08acfe6151d 100644 --- a/src/Aspire.Hosting.Azure/AzureBicepResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure/AzureBicepResourceExtensions.cs @@ -77,6 +77,8 @@ public static BicepSecretOutputReference GetSecretOutput(this IResourceBuilder WithEnvironment(this IResourceBuilder builder, string name, BicepOutputReference bicepOutputReference) where T : IResourceWithEnvironment { + builder.WithResourceRelationship(bicepOutputReference.Resource); + return builder.WithEnvironment(ctx => { ctx.EnvironmentVariables[name] = bicepOutputReference; @@ -205,6 +207,9 @@ public static IResourceBuilder WithParameter(this IResourceBuilder buil where T : AzureBicepResource { BicepIdentifierHelpers.ThrowIfInvalid(name); + + builder.WithResourceRelationship(value); + builder.Resource.Parameters[name] = value; return builder; } @@ -221,6 +226,9 @@ public static IResourceBuilder WithParameter(this IResourceBuilder buil where T : AzureBicepResource { BicepIdentifierHelpers.ThrowIfInvalid(name); + + builder.WithResourceRelationship(value.Resource); + builder.Resource.Parameters[name] = value.Resource; return builder; } @@ -237,6 +245,9 @@ public static IResourceBuilder WithParameter(this IResourceBuilder buil where T : AzureBicepResource { BicepIdentifierHelpers.ThrowIfInvalid(name); + + builder.WithResourceRelationship(value.Resource); + builder.Resource.Parameters[name] = value; return builder; } @@ -253,6 +264,9 @@ public static IResourceBuilder WithParameter(this IResourceBuilder buil where T : AzureBicepResource { BicepIdentifierHelpers.ThrowIfInvalid(name); + + builder.WithResourceRelationship(value); + builder.Resource.Parameters[name] = value; return builder; } @@ -269,6 +283,9 @@ public static IResourceBuilder WithParameter(this IResourceBuilder buil where T : AzureBicepResource { BicepIdentifierHelpers.ThrowIfInvalid(name); + + builder.WithResourceRelationship(value.Resource); + builder.Resource.Parameters[name] = value; return builder; } diff --git a/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs b/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs index 78b6b566d83..d6dd087c7d9 100644 --- a/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs +++ b/src/Aspire.Hosting/ConnectionStringBuilderExtensions.cs @@ -39,6 +39,7 @@ public static IResourceBuilder AddConnectionString(thi { var cs = new ConnectionStringResource(name, connectionStringExpression); return builder.AddResource(cs) + .WithResourceRelationship(connectionStringExpression) .WithInitialState(new CustomResourceSnapshot { ResourceType = "ConnectionString", diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index be274d6ab0a..8cb29a56859 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -52,6 +52,8 @@ public static IResourceBuilder WithEnvironment(this IResourceBuilder bu var expression = value.GetExpression(); + builder.WithResourceRelationship(expression); + return builder.WithEnvironment(context => { context.EnvironmentVariables[name] = expression; @@ -73,6 +75,8 @@ public static IResourceBuilder WithEnvironment(this IResourceBuilder bu ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(value); + builder.WithResourceRelationship(value); + return builder.WithEnvironment(context => { context.EnvironmentVariables[name] = value; @@ -140,6 +144,8 @@ public static IResourceBuilder WithEnvironment(this IResourceBuilder bu ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(endpointReference); + builder.WithResourceRelationship(endpointReference.Resource); + return builder.WithEnvironment(context => { context.EnvironmentVariables[name] = endpointReference; @@ -160,6 +166,8 @@ public static IResourceBuilder WithEnvironment(this IResourceBuilder bu ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(parameter); + builder.WithResourceRelationship(parameter.Resource); + return builder.WithEnvironment(context => { context.EnvironmentVariables[name] = parameter.Resource; @@ -184,6 +192,8 @@ public static IResourceBuilder WithEnvironment( ArgumentNullException.ThrowIfNull(envVarName); ArgumentNullException.ThrowIfNull(resource); + builder.WithResourceRelationship(resource.Resource); + return builder.WithEnvironment(context => { context.EnvironmentVariables[envVarName] = new ConnectionStringReference(resource.Resource, optional: false); @@ -217,6 +227,8 @@ public static IResourceBuilder WithArgs(this IResourceBuilder builder, ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(args); + WalkAndLinkResourceReferences(builder, args); + return builder.WithArgs(context => context.Args.AddRange(args)); } @@ -351,7 +363,7 @@ public static IResourceBuilder WithReference(this IR var resource = source.Resource; connectionName ??= resource.Name; - builder.WithRelationship(resource, KnownRelationshipTypes.Reference); + builder.WithResourceRelationship(resource); return builder.WithEnvironment(context => { @@ -457,7 +469,7 @@ private static void ApplyEndpoints(this IResourceBuilder builder, IResourc endpointReferenceAnnotation.EndpointNames.Add(endpointName); } - builder.WithRelationship(resourceWithEndpoints, KnownRelationshipTypes.Reference); + builder.WithResourceRelationship(resourceWithEndpoints); } /// @@ -1660,6 +1672,89 @@ public static IResourceBuilder WithRelationship( return builder.WithAnnotation(new ResourceRelationshipAnnotation(resource, type)); } + /// + /// Adds a to the resource annotations to add a reference to another resource. + /// + /// The type of the resource. + /// The resource builder. + /// The resource that the relationship is to. + /// A resource builder. + public static IResourceBuilder WithResourceRelationship( + this IResourceBuilder builder, + IResource resource) where T : IResource + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(resource); + + return builder.WithAnnotation(new ResourceRelationshipAnnotation(resource, KnownRelationshipTypes.Reference)); + } + + /// + /// Walks the reference expression and adds s for all resources found in the expression. + /// + /// The type of the resource. + /// The resource builder. + /// The reference expression. + /// A resource builder. + public static IResourceBuilder WithResourceRelationship( + this IResourceBuilder builder, + ReferenceExpression expression) where T : IResource + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(expression); + + WalkAndLinkResourceReferences(builder, expression.ValueProviders); + + return builder; + } + + private static void WalkAndLinkResourceReferences(IResourceBuilder builder, IEnumerable values) + where T : IResource + { + foreach (var value in values) + { + if (value is IResource resource) + { + builder.WithResourceRelationship(resource); + } + else if (value is IResourceBuilder resourceBuilder) + { + builder.WithResourceRelationship(resourceBuilder); + } + else if (value is IValueWithReferences valueWithReferences) + { + foreach (var reference in valueWithReferences.References) + { + if (reference is IResource resourceReference) + { + builder.WithResourceRelationship(resourceReference); + } + else if (reference is IResourceBuilder resourceBuilderReference) + { + builder.WithResourceRelationship(resourceBuilderReference); + } + } + } + } + } + + /// + /// Adds a to the resource annotations to add a reference to another resource. + /// + /// The type of the resource. + /// The resource builder. + /// The resource builder that the relationship is to. + /// A resource builder. + public static IResourceBuilder WithResourceRelationship( + this IResourceBuilder builder, + IResourceBuilder resourceBuilder) where T : IResource + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(resourceBuilder); + + return builder.WithAnnotation(new ResourceRelationshipAnnotation(resourceBuilder.Resource, KnownRelationshipTypes.Reference)); + } + /// /// Adds a to the resource annotations to add a parent-child relationship. /// diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureBicepProvisionerTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureBicepProvisionerTests.cs index 6c1a3f976af..593663eec25 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureBicepProvisionerTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureBicepProvisionerTests.cs @@ -70,6 +70,26 @@ public async Task SetParametersTranslatesCompatibleParameterTypes() Assert.Equal("paramValue", parameters["param"]?["value"]?.ToString()); Assert.Equal("paramValue/1", parameters["expr"]?["value"]?.ToString()); Assert.Equal("http://localhost:1023", parameters["endpoint"]?["value"]?.ToString()); + + // We don't yet process relationships set via the callbacks + // so we don't see the testResource2 nor exe1 + Assert.True(bicep0.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships.DistinctBy(r => (r.Resource, r.Type)), + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(connectionStringResource.Resource, r.Resource); + }, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(param.Resource, r.Resource); + }, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(container.Resource, r.Resource); + }); } [Fact] diff --git a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs index 2c4117e3b29..3013480e1de 100644 --- a/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs +++ b/tests/Aspire.Hosting.Containers.Tests/ContainerResourceTests.cs @@ -120,6 +120,10 @@ public async Task AddContainerWithArgs() arg => Assert.Equal("http://c1:1234", arg), // this is the container hostname arg => Assert.Equal("connectionString", arg)); + // We don't yet process relationships set via the callbacks + // so we don't see the testResource2 nor exe1 + Assert.False(c2.Resource.TryGetAnnotationsOfType(out var relationships)); + var manifest = await ManifestUtils.GetManifest(c2.Resource); var expectedManifest = diff --git a/tests/Aspire.Hosting.Tests/ExecutableResourceTests.cs b/tests/Aspire.Hosting.Tests/ExecutableResourceTests.cs index c1462fbecc1..9914f888447 100644 --- a/tests/Aspire.Hosting.Tests/ExecutableResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/ExecutableResourceTests.cs @@ -48,6 +48,16 @@ public async Task AddExecutableWithArgs() arg => Assert.Equal("anotherConnectionString", arg) ); + Assert.True(exe2.Resource.TryGetAnnotationsOfType(out var relationships)); + // We don't yet process relationships set via the callbacks + // so we don't see the testResource2 nor exe1 + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(testResource, r.Resource); + }); + var manifest = await ManifestUtils.GetManifest(exe2.Resource).DefaultTimeout(); var expectedManifest = diff --git a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs index ab64f600296..bdba1bf321f 100644 --- a/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/ProjectResourceTests.cs @@ -591,6 +591,9 @@ public async Task AddProjectWithArgs() Assert.Collection(args, arg => Assert.Equal("arg1", arg), arg => Assert.Equal("http://localhost:1234", arg)); + + // We don't yet process relationships set via the callbacks + Assert.False(project.Resource.TryGetAnnotationsOfType(out var relationships)); } [Theory] diff --git a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs index 51f8c813819..3fb120baa9f 100644 --- a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs +++ b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs @@ -55,6 +55,14 @@ public async Task EnvironmentReferencingEndpointPopulatesWithBindingUrl() var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectB.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); Assert.Equal("https://localhost:2000", config["myName"]); + + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); + }); } [Fact] @@ -117,6 +125,14 @@ public async Task EnvironmentCallbackPopulatesValueWhenParameterResourceProvided var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectA.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); Assert.Equal("MY_PARAMETER_VALUE", config["MY_PARAMETER"]); + + Assert.True(projectA.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(parameter.Resource, r.Resource); + }); } [Fact] @@ -236,6 +252,19 @@ public async Task EnvironmentVariableExpressions() Assert.Equal("{container1.bindings.primary.port}", manifestConfig["PORT"]); Assert.Equal("{container1.bindings.primary.targetPort}", manifestConfig["TARGET_PORT"]); Assert.Equal("{test.connectionString};name=1", manifestConfig["HOST"]); + + Assert.True(containerB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(container.Resource, r.Resource); + }, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(test.Resource, r.Resource); + }); } [Fact] @@ -289,6 +318,14 @@ public async Task EnvironmentWithConnectionStringSetsProperEnvironmentVariable() // Assert Assert.Single(publishConfig, kvp => kvp.Key == envVarName && kvp.Value == "{sourceService.connectionString}"); + + Assert.True(targetBuilder.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(sourceBuilder.Resource, r.Resource); + }); } private sealed class TestResource(string name, string connectionString) : Resource(name), IResourceWithConnectionString diff --git a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs index a06a53aee86..3572ad4bab0 100644 --- a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs @@ -29,6 +29,14 @@ public async Task ResourceWithSingleEndpointProducesSimplifiedEnvironmentVariabl var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectB.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); + + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); + }); } [Fact] @@ -101,6 +109,14 @@ public async Task ResourceWithConflictingEndpointsProducesAllEnvironmentVariable Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("https://localhost:3000", config["services__projecta__mybinding2__0"]); + + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); + }); } [Fact] @@ -122,6 +138,14 @@ public async Task ResourceWithEndpointsProducesAllEnvironmentVariables() Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("http://localhost:3000", config["services__projecta__mybinding2__0"]); + + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); + }); } [Fact] @@ -268,6 +292,27 @@ public async Task ConnectionStringResourceWithExpressionConnectionString() var servicesKeysCount = config.Keys.Count(k => k.StartsWith("ConnectionStrings__")); Assert.Equal(1, servicesKeysCount); Assert.Equal("Endpoint=http://localhost:3452;Key=secretKey", config["ConnectionStrings__cs"]); + + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(resource.Resource, r.Resource); + }); + + Assert.True(resource.Resource.TryGetAnnotationsOfType(out var csRelationships)); + Assert.Collection(csRelationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(endpoint.Resource, r.Resource); + }, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(key.Resource, r.Resource); + }); } [Fact] @@ -293,6 +338,8 @@ public async Task ConnectionStringResourceWithExpressionConnectionStringBuilder( var servicesKeysCount = config.Keys.Count(k => k.StartsWith("ConnectionStrings__")); Assert.Equal(1, servicesKeysCount); Assert.Equal("Endpoint=http://localhost:3452;Key=secretKey", config["ConnectionStrings__cs"]); + + } [Fact] From d16515b0e85ca011bafd4b1742a1d529f876ca3b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 30 Mar 2025 16:34:29 -0700 Subject: [PATCH 2/3] Remove unnecessary blank lines in WithReferenceTests.cs --- tests/Aspire.Hosting.Tests/WithReferenceTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs index 3572ad4bab0..7de1122c3e6 100644 --- a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs @@ -338,8 +338,6 @@ public async Task ConnectionStringResourceWithExpressionConnectionStringBuilder( var servicesKeysCount = config.Keys.Count(k => k.StartsWith("ConnectionStrings__")); Assert.Equal(1, servicesKeysCount); Assert.Equal("Endpoint=http://localhost:3452;Key=secretKey", config["ConnectionStrings__cs"]); - - } [Fact] From 2cbebf247ca3b4d540fa159d58372fe166eab150 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 30 Mar 2025 17:01:08 -0700 Subject: [PATCH 3/3] Refactor resource linking logic to avoid duplicate references and improve clarity in WalkAndLinkResourceReferences method; add test for same resource in single expression --- .../ResourceBuilderExtensions.cs | 32 ++++++++++++------- .../WithEnvironmentTests.cs | 28 +++++++++++++++- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index 8cb29a56859..4e65e4bf098 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -1711,31 +1711,41 @@ public static IResourceBuilder WithResourceRelationship( private static void WalkAndLinkResourceReferences(IResourceBuilder builder, IEnumerable values) where T : IResource { - foreach (var value in values) + var processed = new HashSet(); + + void AddReference(IResource resource) + { + builder.WithResourceRelationship(resource); + } + + void Walk(object value) { + if (!processed.Add(value)) + { + return; + } + if (value is IResource resource) { - builder.WithResourceRelationship(resource); + AddReference(resource); } else if (value is IResourceBuilder resourceBuilder) { - builder.WithResourceRelationship(resourceBuilder); + AddReference(resourceBuilder.Resource); } else if (value is IValueWithReferences valueWithReferences) { foreach (var reference in valueWithReferences.References) { - if (reference is IResource resourceReference) - { - builder.WithResourceRelationship(resourceReference); - } - else if (reference is IResourceBuilder resourceBuilderReference) - { - builder.WithResourceRelationship(resourceBuilderReference); - } + Walk(reference); } } } + + foreach (var value in values) + { + Walk(value); + } } /// diff --git a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs index 3fb120baa9f..3b63b0e9260 100644 --- a/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs +++ b/tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs @@ -254,7 +254,7 @@ public async Task EnvironmentVariableExpressions() Assert.Equal("{test.connectionString};name=1", manifestConfig["HOST"]); Assert.True(containerB.Resource.TryGetAnnotationsOfType(out var relationships)); - Assert.Collection(relationships, + Assert.Collection(relationships.DistinctBy(r => (r.Resource, r.Type)), r => { Assert.Equal("Reference", r.Type); @@ -267,6 +267,32 @@ public async Task EnvironmentVariableExpressions() }); } + [Fact] + public void EnvironmentVariableSameResourceInSingleExpression() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var container = builder.AddContainer("container1", "image") + .WithHttpEndpoint(name: "primary", targetPort: 10005) + .WithEndpoint("primary", ep => + { + ep.AllocatedEndpoint = new AllocatedEndpoint(ep, "localhost", 90); + }); + + var endpoint = container.GetEndpoint("primary"); + + var containerB = builder.AddContainer("container2", "imageB") + .WithEnvironment("URL", $"{endpoint.Property(EndpointProperty.Host)}:{endpoint.Property(EndpointProperty.Port)}"); + + Assert.True(containerB.Resource.TryGetAnnotationsOfType(out var relationships)); + Assert.Collection(relationships, + r => + { + Assert.Equal("Reference", r.Type); + Assert.Same(container.Resource, r.Resource); + }); + } + [Fact] public async Task EnvironmentVariableWithDynamicTargetPort() {