-
Notifications
You must be signed in to change notification settings - Fork 464
/
ResourceBuilderExtensions.cs
546 lines (494 loc) · 32 KB
/
ResourceBuilderExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Sockets;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Publishing;
using Aspire.Hosting.Utils;
namespace Aspire.Hosting;
/// <summary>
/// Provides extension methods for configuring resources with environment variables.
/// </summary>
public static class ResourceBuilderExtensions
{
private const string ConnectionStringEnvironmentName = "ConnectionStrings__";
/// <summary>
/// Adds an environment variable to the resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the environment variable.</param>
/// <param name="value">The value of the environment variable.</param>
/// <returns>A resource configured with the specified environment variable.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, string name, string? value) where T : IResource
{
return builder.WithAnnotation(new EnvironmentAnnotation(name, value ?? string.Empty));
}
/// <summary>
/// Adds an environment variable to the resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the environment variable.</param>
/// <param name="callback">A callback that allows for deferred execution of a specific environment variable. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connection strings, ports.</param>
/// <returns>A resource configured with the specified environment variable.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, string name, Func<string> callback) where T : IResourceWithEnvironment
{
return builder.WithAnnotation(new EnvironmentCallbackAnnotation(name, callback));
}
/// <summary>
/// Allows for the population of environment variables on a resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="callback">A callback that allows for deferred execution for computing many environment variables. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connection strings, ports.</param>
/// <returns>A resource configured with the environment variable callback.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, Action<EnvironmentCallbackContext> callback) where T : IResourceWithEnvironment
{
return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
}
/// <summary>
/// Allows for the population of environment variables on a resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="callback">A callback that allows for deferred execution for computing many environment variables. This runs after resources have been allocated by the orchestrator and allows access to other resources to resolve computed data, e.g. connection strings, ports.</param>
/// <returns>A resource configured with the environment variable callback.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, Func<EnvironmentCallbackContext, Task> callback) where T : IResourceWithEnvironment
{
return builder.WithAnnotation(new EnvironmentCallbackAnnotation(callback));
}
/// <summary>
/// Adds an environment variable to the resource with the endpoint for <paramref name="endpointReference"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="name">The name of the environment variable.</param>
/// <param name="endpointReference">The endpoint from which to extract the url.</param>
/// <returns>A resource configured with the environment variable callback.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, string name, EndpointReference endpointReference) where T : IResourceWithEnvironment
{
return builder.WithEnvironment(context =>
{
context.EnvironmentVariables[name] = endpointReference;
});
}
/// <summary>
/// Adds an environment variable to the resource with the value from <paramref name="parameter"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="name">Name of environment variable</param>
/// <param name="parameter">Resource builder for the parameter resource.</param>
/// <returns>A resource configured with the environment variable callback.</returns>
public static IResourceBuilder<T> WithEnvironment<T>(this IResourceBuilder<T> builder, string name, IResourceBuilder<ParameterResource> parameter) where T : IResourceWithEnvironment
{
return builder.WithEnvironment(context =>
{
context.EnvironmentVariables[name] = parameter.Resource;
});
}
/// <summary>
/// Registers a callback which is invoked when manifest is generated for the app model.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="callback">Callback method which takes a <see cref="ManifestPublishingContext"/> which can be used to inject JSON into the manifest.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithManifestPublishingCallback<T>(this IResourceBuilder<T> builder, Action<ManifestPublishingContext> callback) where T : IResource
{
// You can only ever have one manifest publishing callback, so it must be a replace operation.
return builder.WithAnnotation(new ManifestPublishingCallbackAnnotation(callback), ResourceAnnotationMutationBehavior.Replace);
}
/// <summary>
/// Registers an async callback which is invoked when manifest is generated for the app model.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="callback">Callback method which takes a <see cref="ManifestPublishingContext"/> which can be used to inject JSON into the manifest.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithManifestPublishingCallback<T>(this IResourceBuilder<T> builder, Func<ManifestPublishingContext, Task> callback) where T : IResource
{
// You can only ever have one manifest publishing callback, so it must be a replace operation.
return builder.WithAnnotation(new ManifestPublishingCallbackAnnotation(callback), ResourceAnnotationMutationBehavior.Replace);
}
/// <summary>
/// Registers a callback which is invoked when a connection string is requested for a resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="resource">Resource to which connection string generation is redirected.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithConnectionStringRedirection<T>(this IResourceBuilder<T> builder, IResourceWithConnectionString resource) where T : IResourceWithConnectionString
{
// You can only ever have one manifest publishing callback, so it must be a replace operation.
return builder.WithAnnotation(new ConnectionStringRedirectAnnotation(resource), ResourceAnnotationMutationBehavior.Replace);
}
private static bool ContainsAmbiguousEndpoints(IEnumerable<AllocatedEndpointAnnotation> endpoints)
{
// An ambiguous endpoint is where any scheme (
return endpoints.GroupBy(e => e.UriScheme).Any(g => g.Count() > 1);
}
private static Action<EnvironmentCallbackContext> CreateEndpointReferenceEnvironmentPopulationCallback<T>(IResourceBuilder<T> builder, EndpointReferenceAnnotation endpointReferencesAnnotation)
where T : IResourceWithEnvironment
{
return (context) =>
{
var name = endpointReferencesAnnotation.Resource.Name;
var allocatedEndPoints = endpointReferencesAnnotation.Resource.Annotations
.OfType<AllocatedEndpointAnnotation>()
.Where(a => endpointReferencesAnnotation.UseAllEndpoints || endpointReferencesAnnotation.EndpointNames.Contains(a.Name));
var containsAmbiguousEndpoints = ContainsAmbiguousEndpoints(allocatedEndPoints);
var replaceLocalhostWithContainerHost = builder.Resource is ContainerResource;
var configuration = builder.ApplicationBuilder.Configuration;
var i = 0;
foreach (var allocatedEndPoint in allocatedEndPoints)
{
var endpointNameQualifiedUriStringKey = $"services__{name}__{i++}";
context.EnvironmentVariables[endpointNameQualifiedUriStringKey] = replaceLocalhostWithContainerHost
? HostNameResolver.ReplaceLocalhostWithContainerHost(allocatedEndPoint.EndpointNameQualifiedUriString, configuration)
: allocatedEndPoint.EndpointNameQualifiedUriString;
if (!containsAmbiguousEndpoints)
{
var uriStringKey = $"services__{name}__{i++}";
context.EnvironmentVariables[uriStringKey] = replaceLocalhostWithContainerHost
? HostNameResolver.ReplaceLocalhostWithContainerHost(allocatedEndPoint.UriString, configuration)
: allocatedEndPoint.UriString;
}
}
};
}
/// <summary>
/// Injects a connection string as an environment variable from the source resource into the destination resource, using the source resource's name as the connection string name (if not overridden).
/// The format of the environment variable will be "ConnectionStrings__{sourceResourceName}={connectionString}."
/// <para>
/// Each resource defines the format of the connection string value. The
/// underlying connection string value can be retrieved using <see cref="IResourceWithConnectionString.GetConnectionString"/>.
/// </para>
/// <para>
/// Connection strings are also resolved by the configuration system (appSettings.json in the AppHost project, or environment variables). If a connection string is not found on the resource, the configuration system will be queried for a connection string
/// using the resource's name.
/// </para>
/// </summary>
/// <typeparam name="TDestination">The destination resource.</typeparam>
/// <param name="builder">The resource where connection string will be injected.</param>
/// <param name="source">The resource from which to extract the connection string.</param>
/// <param name="connectionName">An override of the source resource's name for the connection string. The resulting connection string will be "ConnectionStrings__connectionName" if this is not null.</param>
/// <param name="optional"><see langword="true"/> to allow a missing connection string; <see langword="false"/> to throw an exception if the connection string is not found.</param>
/// <exception cref="DistributedApplicationException">Throws an exception if the connection string resolves to null. It can be null if the resource has no connection string, and if the configuration has no connection string for the source resource.</exception>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, IResourceBuilder<IResourceWithConnectionString> source, string? connectionName = null, bool optional = false)
where TDestination : IResourceWithEnvironment
{
var resource = source.Resource;
connectionName ??= resource.Name;
return builder.WithEnvironment(context =>
{
var connectionStringName = resource.ConnectionStringEnvironmentVariable ?? $"{ConnectionStringEnvironmentName}{connectionName}";
context.EnvironmentVariables[connectionStringName] = new ConnectionStringReference(resource, optional);
});
}
/// <summary>
/// Injects service discovery information as environment variables from the project resource into the destination resource, using the source resource's name as the service name.
/// Each endpoint defined on the project resource will be injected using the format "services__{sourceResourceName}__{endpointIndex}={endpointNameQualifiedUriString}."
/// </summary>
/// <typeparam name="TDestination">The destination resource.</typeparam>
/// <param name="builder">The resource where the service discovery information will be injected.</param>
/// <param name="source">The resource from which to extract service discovery information.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, IResourceBuilder<IResourceWithServiceDiscovery> source)
where TDestination : IResourceWithEnvironment
{
ApplyEndpoints(builder, source.Resource);
return builder;
}
/// <summary>
/// Injects service discovery information as environment variables from the uri into the destination resource, using the name as the service name.
/// The uri will be injected using the format "services__{name}={uri}."
/// </summary>
/// <typeparam name="TDestination"></typeparam>
/// <param name="builder">The resource where the service discovery information will be injected.</param>
/// <param name="name">The name of the service.</param>
/// <param name="uri">The uri of the service.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, string name, Uri uri)
where TDestination : IResourceWithEnvironment
{
if (!uri.IsAbsoluteUri)
{
throw new InvalidOperationException("The uri for service reference must be absolute.");
}
if (uri.AbsolutePath != "/")
{
throw new InvalidOperationException("The uri absolute path must be \"/\".");
}
return builder.WithEnvironment($"services__{name}", uri.ToString());
}
/// <summary>
/// Injects service discovery information from the specified endpoint into the project resource using the source resource's name as the service name.
/// Each endpoint will be injected using the format "services__{sourceResourceName}__{endpointIndex}={endpointNameQualifiedUriString}."
/// </summary>
/// <typeparam name="TDestination">The destination resource.</typeparam>
/// <param name="builder">The resource where the service discovery information will be injected.</param>
/// <param name="endpointReference">The endpoint from which to extract the url.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, EndpointReference endpointReference)
where TDestination : IResourceWithEnvironment
{
ApplyEndpoints(builder, endpointReference.Owner, endpointReference.EndpointName);
return builder;
}
private static void ApplyEndpoints<T>(this IResourceBuilder<T> builder, IResourceWithEndpoints resourceWithEndpoints, string? endpointName = null)
where T : IResourceWithEnvironment
{
// When adding an endpoint we get to see whether there is an EndpointReferenceAnnotation
// on the resource, if there is then it means we have already been here before and we can just
// skip this and note the endpoint that we want to apply to the environment in the future
// in a single pass. There is one EndpointReferenceAnnotation per endpoint source.
var endpointReferenceAnnotation = builder.Resource.Annotations
.OfType<EndpointReferenceAnnotation>()
.Where(sra => sra.Resource == resourceWithEndpoints)
.SingleOrDefault();
if (endpointReferenceAnnotation == null)
{
endpointReferenceAnnotation = new EndpointReferenceAnnotation(resourceWithEndpoints);
builder.WithAnnotation(endpointReferenceAnnotation);
var callback = CreateEndpointReferenceEnvironmentPopulationCallback(builder, endpointReferenceAnnotation);
builder.WithEnvironment(callback);
}
// If no specific endpoint name is specified, go and add all the endpoints.
if (endpointName == null)
{
endpointReferenceAnnotation.UseAllEndpoints = true;
}
else
{
endpointReferenceAnnotation.EndpointNames.Add(endpointName);
}
}
/// <summary>
/// TODO: Doc Comments
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="builder"></param>
/// <param name="hostPort"></param>
/// <param name="scheme"></param>
/// <param name="name"></param>
/// <param name="env"></param>
/// <returns></returns>
[Obsolete("WithServiceBinding has been renamed to WithEndpoint. Use WithEndpoint instead.")]
public static IResourceBuilder<T> WithServiceBinding<T>(this IResourceBuilder<T> builder, int? hostPort = null, string? scheme = null, string? name = null, string? env = null) where T : IResource
{
return builder.WithEndpoint(hostPort: hostPort, scheme: scheme, name: name, env: env);
}
/// <summary>
/// Exposes an endpoint on a resource. This endpoint reference can be retrieved using <see cref="GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be the scheme name if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="scheme">An optional scheme e.g. (http/https). Defaults to "tcp" if not specified.</param>
/// <param name="name">An optional name of the endpoint. Defaults to the scheme name if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithEndpoint<T>(this IResourceBuilder<T> builder, int? hostPort = null, string? scheme = null, string? name = null, string? env = null, bool isProxied = true) where T : IResource
{
if (builder.Resource.Annotations.OfType<EndpointAnnotation>().Any(sb => string.Equals(sb.Name, name, StringComparisons.EndpointAnnotationName)))
{
throw new DistributedApplicationException($"Endpoint '{name}' already exists. Endpoint names are case-insensitive.");
}
var annotation = new EndpointAnnotation(ProtocolType.Tcp, uriScheme: scheme, name: name, port: hostPort, env: env, isProxied: isProxied);
return builder.WithAnnotation(annotation);
}
/// <summary>
/// Changes an existing creates a new endpoint if it doesn't exist and invokes callback to modify the defaults.
/// </summary>
/// <param name="builder">Resource builder for resource with endpoints.</param>
/// <param name="endpointName">Name of endpoint to change.</param>
/// <param name="callback">Callback that modifies the endpoint.</param>
/// <param name="createIfNotExists">Create endpoint if it does not exist.</param>
/// <returns></returns>
public static IResourceBuilder<T> WithEndpoint<T>(this IResourceBuilder<T> builder, string endpointName, Action<EndpointAnnotation> callback, bool createIfNotExists = true) where T : IResourceWithEndpoints
{
var endpoint = builder.Resource.Annotations
.OfType<EndpointAnnotation>()
.Where(ea => StringComparers.EndpointAnnotationName.Equals(ea.Name, endpointName))
.SingleOrDefault();
if (endpoint != null)
{
callback(endpoint);
}
if (endpoint == null && createIfNotExists)
{
endpoint = new EndpointAnnotation(ProtocolType.Tcp, name: endpointName);
callback(endpoint);
builder.Resource.Annotations.Add(endpoint);
}
else if (endpoint == null && !createIfNotExists)
{
return builder;
}
return builder;
}
/// <summary>
/// Exposes an HTTP endpoint on a resource. This endpoint reference can be retrieved using <see cref="GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be "http" if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="name">An optional name of the endpoint. Defaults to "http" if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithHttpEndpoint<T>(this IResourceBuilder<T> builder, int? hostPort = null, string? name = null, string? env = null) where T : IResource
{
return builder.WithEndpoint(hostPort: hostPort, scheme: "http", name: name, env: env);
}
/// <summary>
/// Exposes an HTTPS endpoint on a resource. This endpoint reference can be retrieved using <see cref="GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be "https" if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="name">An optional name of the endpoint. Defaults to "https" if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithHttpsEndpoint<T>(this IResourceBuilder<T> builder, int? hostPort = null, string? name = null, string? env = null) where T : IResource
{
return builder.WithEndpoint(hostPort: hostPort, scheme: "https", name: name, env: env);
}
/// <summary>
/// TODO: Doc Comments
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="builder"></param>
/// <param name="containerPort"></param>
/// <param name="hostPort"></param>
/// <param name="scheme"></param>
/// <param name="name"></param>
/// <param name="env"></param>
/// <returns></returns>
[Obsolete("WithServiceBinding has been renamed to WithEndpoint. Use WithEndpoint instead.")]
public static IResourceBuilder<T> WithServiceBinding<T>(this IResourceBuilder<T> builder, int containerPort, int? hostPort = null, string? scheme = null, string? name = null, string? env = null) where T : IResource
{
return builder.WithEndpoint(containerPort: containerPort, hostPort: hostPort, scheme: scheme, name: name, env: env);
}
/// <summary>
/// Exposes an endpoint on a resource. This endpoint reference can be retrieved using <see cref="ResourceBuilderExtensions.GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be the scheme name if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="containerPort">The container port.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="scheme">An optional scheme e.g. (http/https). Defaults to "tcp" if not specified.</param>
/// <param name="name">An optional name of the endpoint. Defaults to the scheme name if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithEndpoint<T>(this IResourceBuilder<T> builder, int containerPort, int? hostPort = null, string? scheme = null, string? name = null, string? env = null, bool isProxied = true) where T : IResource
{
if (builder.Resource.Annotations.OfType<EndpointAnnotation>().Any(sb => string.Equals(sb.Name, name, StringComparisons.EndpointAnnotationName)))
{
throw new DistributedApplicationException($"Endpoint with name '{name}' already exists");
}
var annotation = new EndpointAnnotation(
protocol: ProtocolType.Tcp,
uriScheme: scheme,
name: name,
port: hostPort,
containerPort: containerPort,
env: env,
isProxied: isProxied);
return builder.WithAnnotation(annotation);
}
/// <summary>
/// Exposes an HTTP endpoint on a resource. This endpoint reference can be retrieved using <see cref="ResourceBuilderExtensions.GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be "http" if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="containerPort">The container port.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="name">An optional name of the endpoint. Defaults to "http" if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithHttpEndpoint<T>(this IResourceBuilder<T> builder, int containerPort, int? hostPort = null, string? name = null, string? env = null, bool isProxied = true) where T : IResource
{
return builder.WithEndpoint(containerPort: containerPort, hostPort: hostPort, scheme: "http", name: name, env: env, isProxied: isProxied);
}
/// <summary>
/// Exposes an HTTPS endpoint on a resource. This endpoint reference can be retrieved using <see cref="ResourceBuilderExtensions.GetEndpoint{T}(IResourceBuilder{T}, string)"/>.
/// The endpoint name will be "https" if not specified.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="containerPort">The container port.</param>
/// <param name="hostPort">An optional host port.</param>
/// <param name="name">An optional name of the endpoint. Defaults to "https" if not specified.</param>
/// <param name="env">An optional name of the environment variable to inject.</param>
/// <param name="isProxied">Specifies if the endpoint will be proxied by DCP. Defaults to true.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
/// <exception cref="DistributedApplicationException">Throws an exception if an endpoint with the same name already exists on the specified resource.</exception>
public static IResourceBuilder<T> WithHttpsEndpoint<T>(this IResourceBuilder<T> builder, int containerPort, int? hostPort = null, string? name = null, string? env = null, bool isProxied = true) where T : IResource
{
return builder.WithEndpoint(containerPort: containerPort, hostPort: hostPort, scheme: "https", name: name, env: env, isProxied: isProxied);
}
/// <summary>
/// Gets an <see cref="EndpointReference"/> by name from the resource. These endpoints are declared either using <see cref="WithEndpoint{T}(IResourceBuilder{T}, int?, string?, string?, string?, bool)"/> or by launch settings (for project resources).
/// The <see cref="EndpointReference"/> can be used to resolve the address of the endpoint in <see cref="WithEnvironment{T}(IResourceBuilder{T}, Action{EnvironmentCallbackContext})"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The the resource builder.</param>
/// <param name="name">The name of the endpoint.</param>
/// <returns>An <see cref="EndpointReference"/> that can be used to resolve the address of the endpoint after resource allocation has occurred.</returns>
public static EndpointReference GetEndpoint<T>(this IResourceBuilder<T> builder, string name) where T : IResourceWithEndpoints
{
return builder.Resource.GetEndpoint(name);
}
/// <summary>
/// Configures a resource to mark all endpoints' transport as HTTP/2. This is useful for HTTP/2 services that need prior knowledge.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> AsHttp2Service<T>(this IResourceBuilder<T> builder) where T : IResourceWithEndpoints
{
return builder.WithAnnotation(new Http2ServiceAnnotation());
}
/// <summary>
/// Excludes a resource from being published to the manifest.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource to exclude.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> ExcludeFromManifest<T>(this IResourceBuilder<T> builder) where T : IResource
{
return builder.WithAnnotation(ManifestPublishingCallbackAnnotation.Ignore);
}
/// <summary>
/// Adds metadata to resource which is output into the manifest.
/// </summary>
/// <typeparam name="T">Type of resource.</typeparam>
/// <param name="builder">Resource builder.</param>
/// <param name="name">Name of metadata.</param>
/// <param name="value">Value of metadata.</param>
/// <returns>Resource builder.</returns>
public static IResourceBuilder<T> WithMetadata<T>(this IResourceBuilder<T> builder, string name, object value) where T : IResource
{
var existingAnnotation = builder.Resource.Annotations.OfType<ManifestMetadataAnnotation>().SingleOrDefault(a => a.Name == name);
if (existingAnnotation != null)
{
builder.Resource.Annotations.Remove(existingAnnotation);
}
return builder.WithAnnotation(new ManifestMetadataAnnotation(name, value));
}
}