diff --git a/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.net6.0.cs b/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.net6.0.cs index 3c261b353a57a..abf1139a2dc34 100644 --- a/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.net6.0.cs +++ b/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.net6.0.cs @@ -110,7 +110,8 @@ public abstract partial class Resource : 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 createProperties) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func)) { } public T Properties { get { throw null; } } - public Azure.Provisioning.Output AddOutput(System.Linq.Expressions.Expression> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false) { throw null; } + public Azure.Provisioning.Output AddOutput(string outputName, System.Linq.Expressions.Expression> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; } + public Azure.Provisioning.Output AddOutput(string outputName, string formattedString, System.Linq.Expressions.Expression> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; } public void AssignProperty(System.Linq.Expressions.Expression> propertySelector, Azure.Provisioning.Parameter parameter) { } public void AssignProperty(System.Linq.Expressions.Expression> propertySelector, string propertyValue) { } } diff --git a/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.netstandard2.0.cs b/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.netstandard2.0.cs index 3c261b353a57a..abf1139a2dc34 100644 --- a/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.netstandard2.0.cs +++ b/sdk/provisioning/Azure.Provisioning/api/Azure.Provisioning.netstandard2.0.cs @@ -110,7 +110,8 @@ public abstract partial class Resource : 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 createProperties) : base (default(Azure.Provisioning.IConstruct), default(Azure.Provisioning.Resource), default(string), default(Azure.Core.ResourceType), default(string), default(System.Func)) { } public T Properties { get { throw null; } } - public Azure.Provisioning.Output AddOutput(System.Linq.Expressions.Expression> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false) { throw null; } + public Azure.Provisioning.Output AddOutput(string outputName, System.Linq.Expressions.Expression> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; } + public Azure.Provisioning.Output AddOutput(string outputName, string formattedString, System.Linq.Expressions.Expression> propertySelector, bool isLiteral = false, bool isSecure = false) { throw null; } public void AssignProperty(System.Linq.Expressions.Expression> propertySelector, Azure.Provisioning.Parameter parameter) { } public void AssignProperty(System.Linq.Expressions.Expression> propertySelector, string propertyValue) { } } diff --git a/sdk/provisioning/Azure.Provisioning/src/Resource.cs b/sdk/provisioning/Azure.Provisioning/src/Resource.cs index d753de4d29b19..afa6b63fc15c8 100644 --- a/sdk/provisioning/Azure.Provisioning/src/Resource.cs +++ b/sdk/provisioning/Azure.Provisioning/src/Resource.cs @@ -215,16 +215,15 @@ private protected void AssignProperty(object instance, string propertyName, stri /// Adds an output to the resource. /// /// The name of the output. - /// The instance which contains the property for the output. - /// The property name to output. /// The expression from the lambda /// Is the output literal. /// Is the output secure. + /// The format string. /// The . - /// If the is not found on the resources properties. - 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; } diff --git a/sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs b/sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs index ee9d68ff676f8..163b448ae2739 100644 --- a/sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs +++ b/sdk/provisioning/Azure.Provisioning/src/ResourceOfT.cs @@ -99,23 +99,39 @@ public void AssignProperty(Expression> propertySelector, Parame /// public void AssignProperty(Expression> propertySelector, string propertyValue) { - (object instance, string name, string expression) = EvaluateLambda(propertySelector); + (object instance, string name, _) = EvaluateLambda(propertySelector); AssignProperty(instance, name, propertyValue); } /// /// Adds an output to the resource. /// - /// /// The name of the output. + /// A lambda expression to select the property to use as the source of the output. + /// Is the output literal. + /// Is the output secure. + /// The . + public Output AddOutput(string outputName, Expression> propertySelector, bool isLiteral = false, bool isSecure = false) + { + (_, _, string expression) = EvaluateLambda(propertySelector, true); + + return AddOutput(outputName, expression, isLiteral, isSecure); + } + + /// + /// Adds an output to the resource. + /// + /// The name of the output. + /// A lambda expression to select the property to use as the source of the output. + /// A tokenized string containing the output. /// Is the output literal. /// Is the output secure. /// The . - public Output AddOutput(Expression> propertySelector, string outputName, bool isLiteral = false, bool isSecure = false) + public Output AddOutput(string outputName, string formattedString, Expression> 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> propertySelector, bool isOutput = false) diff --git a/sdk/provisioning/Azure.Provisioning/src/appconfiguration/AppConfigurationStore.cs b/sdk/provisioning/Azure.Provisioning/src/appconfiguration/AppConfigurationStore.cs index 428e05c1a435e..da4b4c5381e4f 100644 --- a/sdk/provisioning/Azure.Provisioning/src/appconfiguration/AppConfigurationStore.cs +++ b/sdk/provisioning/Azure.Provisioning/src/appconfiguration/AppConfigurationStore.cs @@ -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? creator = null) diff --git a/sdk/provisioning/Azure.Provisioning/tests/ConstructTests.cs b/sdk/provisioning/Azure.Provisioning/tests/ConstructTests.cs index 5f919f96e1bc8..dff05d3e54dcd 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/ConstructTests.cs +++ b/sdk/provisioning/Azure.Provisioning/tests/ConstructTests.cs @@ -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()); @@ -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; diff --git a/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/CognitiveServices/main.bicep b/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/CognitiveServices/main.bicep index 7aa72dc21e7e4..78add1260ef1c 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/CognitiveServices/main.bicep +++ b/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/CognitiveServices/main.bicep @@ -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) diff --git a/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/ServiceBus/main.bicep b/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/ServiceBus/main.bicep index 1e8dc1114c82d..f280b328822b7 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/ServiceBus/main.bicep +++ b/sdk/provisioning/Azure.Provisioning/tests/Infrastructure/ServiceBus/main.bicep @@ -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) diff --git a/sdk/provisioning/Azure.Provisioning/tests/ProvisioningTests.cs b/sdk/provisioning/Azure.Provisioning/tests/ProvisioningTests.cs index d3d98f10c9013..9360f38c3bf39 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/ProvisioningTests.cs +++ b/sdk/provisioning/Azure.Provisioning/tests/ProvisioningTests.cs @@ -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)); @@ -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)); @@ -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(() => kv.Properties.Tags.Add("key", "value")); @@ -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 @@ -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); @@ -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)); diff --git a/sdk/provisioning/Azure.Provisioning/tests/TestCommonSqlDatabase.cs b/sdk/provisioning/Azure.Provisioning/tests/TestCommonSqlDatabase.cs index ed4cd5925377f..3c2a1d11b3359 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/TestCommonSqlDatabase.cs +++ b/sdk/provisioning/Azure.Provisioning/tests/TestCommonSqlDatabase.cs @@ -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); diff --git a/sdk/provisioning/Azure.Provisioning/tests/TestFrontEndWebSite.cs b/sdk/provisioning/Azure.Provisioning/tests/TestFrontEndWebSite.cs index b87aa71678192..4cf5c30e1c078 100644 --- a/sdk/provisioning/Azure.Provisioning/tests/TestFrontEndWebSite.cs +++ b/sdk/provisioning/Azure.Provisioning/tests/TestFrontEndWebSite.cs @@ -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); }