Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support adding outputs that use interpolated strings #42597

Merged
merged 3 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public abstract partial class Resource<T> : Azure.Provisioning.Resource where T
{
protected Resource(Azure.Provisioning.IConstruct scope, Azure.Provisioning.Resource? parent, string resourceName, Azure.Core.ResourceType resourceType, string version, System.Func<string, T> createProperties) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, object>)) { }
public T Properties { get { throw null; } }
public Azure.Provisioning.Output AddOutput(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false) { throw null; }
public Azure.Provisioning.Output AddOutput(string outputName, System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; }
public Azure.Provisioning.Output AddOutput(string outputName, string formattedString, System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; }
public void AssignProperty(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, Azure.Provisioning.Parameter parameter) { }
public void AssignProperty(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, string propertyValue) { }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public abstract partial class Resource<T> : Azure.Provisioning.Resource where T
{
protected Resource(Azure.Provisioning.IConstruct scope, Azure.Provisioning.Resource? parent, string resourceName, Azure.Core.ResourceType resourceType, string version, System.Func<string, T> createProperties) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func<string, object>)) { }
public T Properties { get { throw null; } }
public Azure.Provisioning.Output AddOutput(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false) { throw null; }
public Azure.Provisioning.Output AddOutput(string outputName, System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; }
public Azure.Provisioning.Output AddOutput(string outputName, string formattedString, System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; }
public void AssignProperty(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, Azure.Provisioning.Parameter parameter) { }
public void AssignProperty(System.Linq.Expressions.Expression<System.Func<T, object?>> propertySelector, string propertyValue) { }
}
Expand Down
9 changes: 4 additions & 5 deletions sdk/provisioning/Azure.Provisioning/src/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,16 +215,15 @@ private protected void AssignProperty(object instance, string propertyName, stri
/// Adds an output to the resource.
/// </summary>
/// <param name="name">The name of the output.</param>
/// <param name="instance">The instance which contains the property for the output.</param>
/// <param name="propertyName">The property name to output.</param>
/// <param name="expression">The expression from the lambda</param>
/// <param name="isLiteral">Is the output literal.</param>
/// <param name="isSecure">Is the output secure.</param>
/// <param name="formatString">The format string.</param>
/// <returns>The <see cref="Output"/>.</returns>
/// <exception cref="ArgumentException">If the <paramref name="propertyName"/> is not found on the resources properties.</exception>
private protected Output AddOutput(string name, object instance, string propertyName, string expression, bool isLiteral = false, bool isSecure = false)
private protected Output AddOutput(string name, string expression, bool isLiteral = false, bool isSecure = false, string? formatString = default)
{
var result = new Output(name, $"{Name}.{expression}", Scope, this, isLiteral, isSecure);
string? formatted = formatString != null ? string.Format(formatString, $"{Name}.{expression}") : $"{Name}.{expression}";
var result = new Output(name, formatted, Scope, this, isLiteral, isSecure);
Scope.AddOutput(result);
return result;
}
Expand Down
26 changes: 21 additions & 5 deletions sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,39 @@ public void AssignProperty(Expression<Func<T, object?>> propertySelector, Parame
/// <exception cref="NotSupportedException"></exception>
public void AssignProperty(Expression<Func<T, object?>> propertySelector, string propertyValue)
{
(object instance, string name, string expression) = EvaluateLambda(propertySelector);
(object instance, string name, _) = EvaluateLambda(propertySelector);
AssignProperty(instance, name, propertyValue);
}

/// <summary>
/// Adds an output to the resource.
/// </summary>
/// <param name="propertySelector"></param>
/// <param name="outputName">The name of the output.</param>
/// <param name="propertySelector">A lambda expression to select the property to use as the source of the output.</param>
/// <param name="isLiteral">Is the output literal.</param>
/// <param name="isSecure">Is the output secure.</param>
/// <returns>The <see cref="Output"/>.</returns>
public Output AddOutput(string outputName, Expression<Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false)
{
(_, _, string expression) = EvaluateLambda(propertySelector, true);

return AddOutput(outputName, expression, isLiteral, isSecure);
}

/// <summary>
/// Adds an output to the resource.
/// </summary>
/// <param name="outputName">The name of the output.</param>
/// <param name="propertySelector">A lambda expression to select the property to use as the source of the output.</param>
/// <param name="formattedString">A tokenized string containing the output.</param>
/// <param name="isLiteral">Is the output literal.</param>
/// <param name="isSecure">Is the output secure.</param>
/// <returns>The <see cref="Output"/>.</returns>
public Output AddOutput(Expression<Func<T, object?>> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false)
public Output AddOutput(string outputName, string formattedString, Expression<Func<T, object?>> propertySelector, bool isLiteral = false, bool isSecure = false)
{
(object instance, string name, string expression) = EvaluateLambda(propertySelector, true);
(_, _, string expression) = EvaluateLambda(propertySelector, true);

return AddOutput(outputName, instance, name, expression, isLiteral, isSecure);
return AddOutput(outputName, expression, isLiteral, isSecure, formattedString);
}

private (object Instance, string PropertyName, string Expression) EvaluateLambda(Expression<Func<T, object?>> propertySelector, bool isOutput = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public AppConfigurationStore(IConstruct scope, ResourceGroup? parent = null, str
location: location ?? Environment.GetEnvironmentVariable("AZURE_LOCATION") ?? AzureLocation.WestUS,
skuName: "free"))
{
AddOutput(store => store.Endpoint, $"{Name}_endpoint");
AddOutput($"{Name}_endpoint", store => store.Endpoint);
}

private AppConfigurationStore(IConstruct scope, ResourceGroup? parent = null, string name = "store", string version = "2023-03-01", bool isExisting = false, Func<string, AppConfigurationStoreData>? creator = null)
Expand Down
6 changes: 3 additions & 3 deletions sdk/provisioning/Azure.Provisioning/tests/ConstructTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void GetOutputsNoChildConstructs(bool recursive)
var infra = new TestInfrastructure();
var rg1 = new ResourceGroup(infra, "rg1");

rg1.AddOutput(r => r.Location, "location");
rg1.AddOutput("location", r => r.Location);

var outputs = infra.GetOutputs(recursive);
Assert.AreEqual(1, outputs.Count());
Expand All @@ -137,11 +137,11 @@ public void GetOutputsChildConstructs(bool recursive)
{
var infra = new TestInfrastructure();
var rg1 = new ResourceGroup(infra, "rg1");
rg1.AddOutput(r => r.Location, "location");
rg1.AddOutput("location", r => r.Location);

var childScope = infra.AddFrontEndWebSite();
var rg2 = new ResourceGroup(childScope, "rg2");
rg2.AddOutput(r => r.Location, "location");
rg2.AddOutput("location", r => r.Location);

// front end website has an output
var expected = recursive ? 4 : 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ resource cognitiveServicesAccountDeployment_JeeW2XLVR 'Microsoft.CognitiveServic
}
}
}

output endpoint string = 'Endpoint=${cognitiveServicesAccount_ZfMvJY5Po.properties.endpoint}'
output expression string = uniqueString(cognitiveServicesAccount_ZfMvJY5Po.properties.endpoint)
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ resource roleAssignment_hjlGLL4Xr 'Microsoft.Authorization/roleAssignments@2022-
principalType: 'ServicePrincipal'
}
}

output endpoint string = 'Endpoint=${serviceBusNamespace_VkKO9fgDH.properties.serviceBusEndpoint}'
output expression string = uniqueString(serviceBusNamespace_VkKO9fgDH.properties.serviceBusEndpoint)
21 changes: 11 additions & 10 deletions sdk/provisioning/Azure.Provisioning/tests/ProvisioningTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ public async Task WebSiteUsingL1()

Assert.AreEqual(Guid.Empty.ToString(), frontEnd.Properties.AppServicePlanId.SubscriptionId);

var frontEndPrincipalId = frontEnd.AddOutput(
website => website.Identity.PrincipalId, //Identity.PrincipalId
var frontEndPrincipalId = frontEnd.AddOutput(//Identity.PrincipalId
"SERVICE_API_IDENTITY_PRINCIPAL_ID",
isSecure: true);
website => website.Identity.PrincipalId, isSecure: true);

var kv = infra.AddKeyVault();
kv.AddAccessPolicy(frontEndPrincipalId); // frontEnd.properties.identity.principalId
kv.AssignRole(RoleDefinition.KeyVaultAdministrator, Guid.Empty);
kv.AddOutput(data => data.Properties.VaultUri, "vaultUri");
kv.AddOutput("vaultUri", data => data.Properties.VaultUri);

KeyVaultSecret sqlAdminSecret = new KeyVaultSecret(infra, name: "sqlAdminPassword");
Assert.False(sqlAdminSecret.Properties.Name.EndsWith(infra.EnvironmentName));
Expand All @@ -86,7 +85,7 @@ public async Task WebSiteUsingL1()
SqlServer sqlServer = new SqlServer(infra, "sqlserver");
sqlServer.AssignProperty(sql => sql.AdministratorLogin, "'sqladmin'");
sqlServer.AssignProperty(sql => sql.AdministratorLoginPassword, sqlAdminPasswordParam);
Output sqlServerName = sqlServer.AddOutput(sql => sql.FullyQualifiedDomainName, "sqlServerName");
Output sqlServerName = sqlServer.AddOutput("sqlServerName", sql => sql.FullyQualifiedDomainName);

SqlDatabase sqlDatabase = new SqlDatabase(infra, sqlServer);
Assert.False(sqlDatabase.Properties.Name.EndsWith(infra.EnvironmentName));
Expand Down Expand Up @@ -251,7 +250,7 @@ public async Task RedisCacheWithExistingKeyVault()
TestInfrastructure infrastructure = new TestInfrastructure(configuration: new Configuration { UseInteractiveMode = true });
var cache = new RedisCache(infrastructure);
var kv = KeyVault.FromExisting(infrastructure, name: "'existingVault'");
kv.AddOutput(data => data.Properties.VaultUri, "vaultUri");
kv.AddOutput("vaultUri", data => data.Properties.VaultUri);

// can't mutate existing resource
Assert.Throws<InvalidOperationException>(() => kv.Properties.Tags.Add("key", "value"));
Expand Down Expand Up @@ -351,7 +350,8 @@ public void CognitiveServices()
TestInfrastructure infrastructure = new TestInfrastructure(configuration: new Configuration { UseInteractiveMode = true });
var account = new CognitiveServicesAccount(infrastructure, location: AzureLocation.EastUS);
account.AssignProperty(data => data.Properties.PublicNetworkAccess, new Parameter("publicNetworkAccess", defaultValue: "Enabled"));

account.AddOutput("endpoint", "'Endpoint=${{{0}}}'", data => data.Properties.Endpoint);
account.AddOutput("expression", "uniqueString({0})", data => data.Properties.Endpoint);
_ = new CognitiveServicesAccountDeployment(
infrastructure,
new CognitiveServicesAccountDeploymentModel
Expand All @@ -377,7 +377,8 @@ public async Task ServiceBus()
var topic = new ServiceBusTopic(infrastructure, parent: account);
_ = new ServiceBusSubscription(infrastructure, parent: topic);
account.AssignRole(RoleDefinition.ServiceBusDataOwner, Guid.Empty);

account.AddOutput("endpoint", "'Endpoint=${{{0}}}'", data => data.ServiceBusEndpoint);
account.AddOutput("expression", "uniqueString({0})", data => data.ServiceBusEndpoint);
infrastructure.Build(GetOutputPath());

await ValidateBicepAsync(interactiveMode: true);
Expand Down Expand Up @@ -649,8 +650,8 @@ public async Task OutputsSpanningModules()
var appServicePlan = infra.AddAppServicePlan(parent: rg1);
WebSite frontEnd1 = new WebSite(infra, "frontEnd", appServicePlan, WebSiteRuntime.Node, "18-lts", parent: rg1);

var output1 = frontEnd1.AddOutput(data => data.Identity.PrincipalId, "STORAGE_PRINCIPAL_ID");
var output2 = frontEnd1.AddOutput(data => data.Location, "LOCATION");
var output1 = frontEnd1.AddOutput("STORAGE_PRINCIPAL_ID", data => data.Identity.PrincipalId);
var output2 = frontEnd1.AddOutput("LOCATION", data => data.Location);

KeyVault keyVault = infra.AddKeyVault(resourceGroup: rg1);
keyVault.AssignProperty(data => data.Properties.EnableSoftDelete, new Parameter("enableSoftDelete", "Enable soft delete", defaultValue: true, isSecure: false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public TestCommonSqlDatabase(IConstruct scope, KeyVault? keyVault = null)
SqlServer sqlServer = new SqlServer(this, "sqlserver");
sqlServer.AssignProperty(sql => sql.AdministratorLoginPassword, sqlAdminPasswordParam);
sqlServer.AssignProperty(sql => sql.AdministratorLogin, "'sqladmin'");
Output sqlServerName = sqlServer.AddOutput(sql => sql.FullyQualifiedDomainName, "sqlServerName");
Output sqlServerName = sqlServer.AddOutput("sqlServerName", sql => sql.FullyQualifiedDomainName);

SqlDatabase = new SqlDatabase(this, sqlServer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ public TestFrontEndWebSite(IConstruct scope, KeyVault? keyVault = null, AppServi

WebSite frontEnd = new WebSite(this, "frontEnd", appServicePlan, WebSiteRuntime.Node, "18-lts");

var frontEndPrincipalId = frontEnd.AddOutput(
website => website.Identity.PrincipalId,
"SERVICE_API_IDENTITY_PRINCIPAL_ID",
isSecure: true);
var frontEndPrincipalId = frontEnd.AddOutput("SERVICE_API_IDENTITY_PRINCIPAL_ID",
website => website.Identity.PrincipalId, isSecure: true);

keyVault.AddAccessPolicy(frontEndPrincipalId);
keyVault.AddOutput(data => data.Properties.VaultUri, "vaultUri");
keyVault.AddOutput("vaultUri", data => data.Properties.VaultUri);

WebSiteConfigLogs logs = new WebSiteConfigLogs(this, "logs", frontEnd);
}
Expand Down