-
Notifications
You must be signed in to change notification settings - Fork 350
/
AzureConstructResource.cs
196 lines (164 loc) · 8.35 KB
/
AzureConstructResource.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable AZPROVISION001
using System.Linq.Expressions;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
namespace Aspire.Hosting;
/// <summary>
/// An Aspire resource that supports use of Azure Provisioning APIs to create Azure resources.
/// </summary>
/// <param name="name">The name of the construct in the Aspire application model.</param>
/// <param name="configureConstruct">Callback to populate the construct with Azure resources.</param>
public class AzureConstructResource(string name, Action<ResourceModuleConstruct> configureConstruct) : AzureBicepResource(name, templateFile: $"{name}.module.bicep")
{
/// <summary>
/// Callback for configuring construct.
/// </summary>
public Action<ResourceModuleConstruct> ConfigureConstruct { get; } = configureConstruct;
/// <inheritdoc/>
public override BicepTemplateFile GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true)
{
var configuration = new Configuration()
{
UseInteractiveMode = true
};
var resourceModuleConstruct = new ResourceModuleConstruct(this, configuration);
ConfigureConstruct(resourceModuleConstruct);
// WARNING: GetParameters currently returns more than one instance of the same
// parameter. Its the only API that gives us what we need (a list of
// parameters. Here we find all the distinct parameters by name and
// put them into a dictionary for quick lookup so we don't need to scan
// through the parameter enumerable each time.
var constructParameters = resourceModuleConstruct.GetParameters();
var distinctConstructParameters = constructParameters.DistinctBy(p => p.Name);
var distinctConstructParametersLookup = distinctConstructParameters.ToDictionary(p => p.Name);
foreach (var aspireParameter in this.Parameters)
{
if (distinctConstructParametersLookup.ContainsKey(aspireParameter.Key))
{
continue;
}
var constructParameter = new Parameter(aspireParameter.Key);
resourceModuleConstruct.AddParameter(constructParameter);
}
var generationPath = Directory.CreateTempSubdirectory("aspire").FullName;
resourceModuleConstruct.Build(generationPath);
var moduleSourcePath = Path.Combine(generationPath, "main.bicep");
var moduleDestinationPath = Path.Combine(directory ?? generationPath, $"{Name}.module.bicep");
File.Copy(moduleSourcePath, moduleDestinationPath, true);
return new BicepTemplateFile(moduleDestinationPath, directory is null);
}
private string? _generatedBicep;
/// <inheritdoc />
public override string GetBicepTemplateString()
{
if (_generatedBicep is null)
{
var template = GetBicepTemplateFile();
_generatedBicep = File.ReadAllText(template.Path);
}
return _generatedBicep;
}
}
/// <summary>
/// Extensions for working with <see cref="AzureConstructResource"/> and related types.
/// </summary>
public static class AzureConstructResourceExtensions
{
/// <summary>
/// Adds an Azure construct resource to the application model.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="name">The name of the resource being added.</param>
/// <param name="configureConstruct">A callback used to configure the construct resource.</param>
/// <returns></returns>
public static IResourceBuilder<AzureConstructResource> AddAzureConstruct(this IDistributedApplicationBuilder builder, string name, Action<ResourceModuleConstruct> configureConstruct)
{
builder.AddAzureProvisioning();
var resource = new AzureConstructResource(name, configureConstruct);
return builder.AddResource(resource)
.WithManifestPublishingCallback(resource.WriteToManifest);
}
/// <summary>
/// Assigns an Aspire parameter resource to an Azure construct resource.
/// </summary>
/// <typeparam name="T">Type of the CDK resource.</typeparam>
/// <param name="resource">The CDK resource.</param>
/// <param name="propertySelector">Property selection expression.</param>
/// <param name="parameterResourceBuilder">Aspire parameter resource builder.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
public static void AssignProperty<T>(this Resource<T> resource, Expression<Func<T, object?>> propertySelector, IResourceBuilder<ParameterResource> parameterResourceBuilder, string? parameterName = null) where T: notnull
{
parameterName ??= parameterResourceBuilder.Resource.Name;
if (resource.Scope is not ResourceModuleConstruct construct)
{
throw new ArgumentException("Cannot bind Aspire parameter resource to this construct.", nameof(resource));
}
construct.Resource.Parameters[parameterName] = parameterResourceBuilder.Resource;
if (resource.Scope.GetParameters().Any(p => p.Name == parameterName))
{
var parameter = resource.Scope.GetParameters().Single(p => p.Name == parameterName);
resource.AssignProperty(propertySelector, parameter.Name);
}
else
{
var parameter = new Parameter(parameterName, isSecure: parameterResourceBuilder.Resource.Secret);
resource.AssignProperty(propertySelector, parameter);
}
}
/// <summary>
/// Assigns an Aspire Bicep output reference to an Azure construct resource.
/// </summary>
/// <typeparam name="T">Type of the CDK resource.</typeparam>
/// <param name="resource">The CDK resource.</param>
/// <param name="propertySelector">Property selection expression.</param>
/// <param name="parameterName">The name of the parameter to be assigned.</param>
/// <param name="outputReference">Aspire parameter resource builder.</param>
public static void AssignProperty<T>(this Resource<T> resource, Expression<Func<T, object?>> propertySelector, BicepOutputReference outputReference, string? parameterName = null) where T : notnull
{
parameterName ??= outputReference.Resource.Name;
if (resource.Scope is not ResourceModuleConstruct construct)
{
throw new ArgumentException("Cannot bind Aspire parameter resource to this construct.", nameof(resource));
}
construct.Resource.Parameters[parameterName] = outputReference;
if (resource.Scope.GetParameters().Any(p => p.Name == parameterName))
{
var parameter = resource.Scope.GetParameters().Single(p => p.Name == parameterName);
resource.AssignProperty(propertySelector, parameter);
}
else
{
var parameter = new Parameter(parameterName);
resource.AssignProperty(propertySelector, parameter);
}
}
}
/// <summary>
/// An Azure Provisioning construct which represents the root Bicep module that is generated for an Azure construct resource.
/// </summary>
public class ResourceModuleConstruct : Infrastructure
{
internal ResourceModuleConstruct(AzureConstructResource resource, Configuration configuration) : base(constructScope: ConstructScope.ResourceGroup, tenantId: Guid.Empty, subscriptionId: Guid.Empty, envName: "temp", configuration: configuration)
{
Resource = resource;
}
/// <summary>
/// The Azure construct resource that this resource module construct represents.
/// </summary>
public AzureConstructResource Resource { get; }
/// <summary>
/// The common principalId parameter injected into most Aspire-based Bicep files.
/// </summary>
public Parameter PrincipalIdParameter => new Parameter("principalId");
/// <summary>
/// The common principalType parameter injected into most Aspire-based Bicep files.
/// </summary>
public Parameter PrincipalTypeParameter => new Parameter("principalType");
/// <summary>
/// The common principalName parameter injected into some Aspire-based Bicep files.
/// </summary>
public Parameter PrincipalNameParameter => new Parameter("principalName");
}