-
Notifications
You must be signed in to change notification settings - Fork 373
/
EndpointReference.cs
230 lines (195 loc) · 9.06 KB
/
EndpointReference.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
namespace Aspire.Hosting.ApplicationModel;
/// <summary>
/// Represents an endpoint reference for a resource with endpoints.
/// </summary>
public sealed class EndpointReference : IManifestExpressionProvider, IValueProvider, IValueWithReferences
{
// A reference to the endpoint annotation if it exists.
private EndpointAnnotation? _endpointAnnotation;
private bool? _isAllocated;
// TODO: Expose this
internal EndpointAnnotation EndpointAnnotation => GetEndpointAnnotation() ?? throw new InvalidOperationException($"The endpoint `{EndpointName}` is not defined for the resource `{Resource.Name}`.");
/// <summary>
/// Gets the resource owner of the endpoint reference.
/// </summary>
public IResourceWithEndpoints Resource { get; }
IEnumerable<object> IValueWithReferences.References => [Resource];
/// <summary>
/// Gets the name of the endpoint associated with the endpoint reference.
/// </summary>
public string EndpointName { get; }
/// <summary>
/// Gets a value indicating whether the endpoint is allocated.
/// </summary>
public bool IsAllocated => _isAllocated ??= GetAllocatedEndpoint() is not null;
/// <summary>
/// Gets a value indicating whether the endpoint exists.
/// </summary>
public bool Exists => GetEndpointAnnotation() is not null;
string IManifestExpressionProvider.ValueExpression => GetExpression();
ValueTask<string?> IValueProvider.GetValueAsync(CancellationToken cancellationToken) => new(Url);
/// <summary>
/// Gets the specified property expression of the endpoint. Defaults to the URL if no property is specified.
/// </summary>
internal string GetExpression(EndpointProperty property = EndpointProperty.Url)
{
var prop = property switch
{
EndpointProperty.Url => "url",
EndpointProperty.Host or EndpointProperty.IPV4Host => "host",
EndpointProperty.Port => "port",
EndpointProperty.Scheme => "scheme",
EndpointProperty.TargetPort => "targetPort",
_ => throw new InvalidOperationException($"The property '{property}' is not supported for the endpoint '{EndpointName}'.")
};
return $"{{{Resource.Name}.bindings.{EndpointName}.{prop}}}";
}
/// <summary>
/// Gets the specified property expression of the endpoint. Defaults to the URL if no property is specified.
/// </summary>
/// <param name="property">The <see cref="EndpointProperty"/> enum value to use in the reference.</param>
/// <returns>An <see cref="EndpointReferenceExpression"/> representing the specified <see cref="EndpointProperty"/>.</returns>
public EndpointReferenceExpression Property(EndpointProperty property)
{
return new(this, property);
}
/// <summary>
/// Gets the port for this endpoint.
/// </summary>
public int Port => AllocatedEndpoint.Port;
/// <summary>
/// Gets the target port for this endpoint. If the port is dynamically allocated, this will return <see langword="null"/>.
/// </summary>
public int? TargetPort => EndpointAnnotation.TargetPort;
/// <summary>
/// Gets the host for this endpoint.
/// </summary>
public string Host => AllocatedEndpoint.Address ?? "localhost";
/// <summary>
/// Gets the container host for this endpoint.
/// </summary>
public string ContainerHost => AllocatedEndpoint.ContainerHostAddress ?? throw new InvalidOperationException($"The endpoint \"{EndpointName}\" has no associated container host name.");
/// <summary>
/// Gets the scheme for this endpoint.
/// </summary>
public string Scheme => EndpointAnnotation.UriScheme;
/// <summary>
/// Gets the URL for this endpoint.
/// </summary>
public string Url => AllocatedEndpoint.UriString;
internal AllocatedEndpoint AllocatedEndpoint =>
GetAllocatedEndpoint()
?? throw new InvalidOperationException($"The endpoint `{EndpointName}` is not allocated for the resource `{Resource.Name}`.");
private EndpointAnnotation? GetEndpointAnnotation() =>
_endpointAnnotation ??= Resource.Annotations.OfType<EndpointAnnotation>().SingleOrDefault(a => StringComparers.EndpointAnnotationName.Equals(a.Name, EndpointName));
private AllocatedEndpoint? GetAllocatedEndpoint() => GetEndpointAnnotation()?.AllocatedEndpoint;
/// <summary>
/// Creates a new instance of <see cref="EndpointReference"/> with the specified endpoint name.
/// </summary>
/// <param name="owner">The resource with endpoints that owns the endpoint reference.</param>
/// <param name="endpoint">The endpoint annotation.</param>
public EndpointReference(IResourceWithEndpoints owner, EndpointAnnotation endpoint)
{
ArgumentNullException.ThrowIfNull(owner);
ArgumentNullException.ThrowIfNull(endpoint);
Resource = owner;
EndpointName = endpoint.Name;
_endpointAnnotation = endpoint;
}
/// <summary>
/// Creates a new instance of <see cref="EndpointReference"/> with the specified endpoint name.
/// </summary>
/// <param name="owner">The resource with endpoints that owns the endpoint reference.</param>
/// <param name="endpointName">The name of the endpoint.</param>
public EndpointReference(IResourceWithEndpoints owner, string endpointName)
{
ArgumentNullException.ThrowIfNull(owner);
ArgumentNullException.ThrowIfNull(endpointName);
Resource = owner;
EndpointName = endpointName;
}
}
/// <summary>
/// Represents a property expression for an endpoint reference.
/// </summary>
/// <param name="endpointReference">The endpoint reference.</param>
/// <param name="property">The property of the endpoint.</param>
public class EndpointReferenceExpression(EndpointReference endpointReference, EndpointProperty property) : IManifestExpressionProvider, IValueProvider, IValueWithReferences
{
/// <summary>
/// Gets the <see cref="EndpointReference"/>.
/// </summary>
public EndpointReference Endpoint { get; } = endpointReference ?? throw new ArgumentNullException(nameof(endpointReference));
/// <summary>
/// Gets the <see cref="EndpointProperty"/> for the property expression.
/// </summary>
public EndpointProperty Property { get; } = property;
/// <summary>
/// Gets the expression of the property of the endpoint.
/// </summary>
public string ValueExpression =>
Endpoint.GetExpression(Property);
/// <summary>
/// Gets the value of the property of the endpoint.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
/// <returns>A <see cref="string"/> containing the selected <see cref="EndpointProperty"/> value.</returns>
/// <exception cref="InvalidOperationException">Throws when the selected <see cref="EndpointProperty"/> enumeration is not known.</exception>
public ValueTask<string?> GetValueAsync(CancellationToken cancellationToken) => Property switch
{
EndpointProperty.Url => new(Endpoint.Url),
EndpointProperty.Host => new(Endpoint.Host),
EndpointProperty.IPV4Host => new("127.0.0.1"),
EndpointProperty.Port => new(Endpoint.Port.ToString(CultureInfo.InvariantCulture)),
EndpointProperty.Scheme => new(Endpoint.Scheme),
EndpointProperty.TargetPort => new(ComputeTargetPort()),
_ => throw new InvalidOperationException($"The property '{Property}' is not supported for the endpoint '{Endpoint.EndpointName}'.")
};
private string? ComputeTargetPort()
{
// We have a target port, so we can return it directly.
if (Endpoint.TargetPort is int port)
{
return port.ToString(CultureInfo.InvariantCulture);
}
// There is no way to resolve the value of the target port until runtime. Even then, replicas make this very complex because
// the target port is not known until the replica is allocated.
// Instead, we return an expression that will be resolved at runtime by the orchestrator.
return Endpoint.AllocatedEndpoint.TargetPortExpression
?? throw new InvalidOperationException("The endpoint does not have an associated TargetPortExpression from the orchestrator.");
}
IEnumerable<object> IValueWithReferences.References => [Endpoint];
}
/// <summary>
/// Represents the properties of an endpoint that can be referenced.
/// </summary>
public enum EndpointProperty
{
/// <summary>
/// The entire URL of the endpoint.
/// </summary>
Url,
/// <summary>
/// The host of the endpoint.
/// </summary>
Host,
/// <summary>
/// The IPv4 address of the endpoint.
/// </summary>
IPV4Host,
/// <summary>
/// The port of the endpoint.
/// </summary>
Port,
/// <summary>
/// The scheme of the endpoint.
/// </summary>
Scheme,
/// <summary>
/// The target port of the endpoint.
/// </summary>
TargetPort
}