2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Globalization ;
5
- using System . Text ;
6
5
using System . Text . RegularExpressions ;
7
6
using Aspire . Hosting . ApplicationModel ;
8
7
using Aspire . Hosting . Lifecycle ;
12
11
using Azure . Provisioning . KeyVault ;
13
12
using Azure . Provisioning . Resources ;
14
13
using Microsoft . Extensions . Logging ;
14
+ using Microsoft . Extensions . Options ;
15
15
16
16
namespace Aspire . Hosting . Azure ;
17
17
18
18
/// <summary>
19
19
/// Represents the infrastructure for Azure Container Apps within the Aspire Hosting environment.
20
20
/// Implements the <see cref="IDistributedApplicationLifecycleHook"/> interface to provide lifecycle hooks for distributed applications.
21
21
/// </summary>
22
- internal sealed class AzureContainerAppsInfrastructure ( ILogger < AzureContainerAppsInfrastructure > logger , DistributedApplicationExecutionContext executionContext ) : IDistributedApplicationLifecycleHook
22
+ internal sealed class AzureContainerAppsInfrastructure (
23
+ ILogger < AzureContainerAppsInfrastructure > logger ,
24
+ IOptions < AzureProvisioningOptions > provisioningOptions ,
25
+ DistributedApplicationExecutionContext executionContext ) : IDistributedApplicationLifecycleHook
23
26
{
24
27
public async Task BeforeStartAsync ( DistributedApplicationModel appModel , CancellationToken cancellationToken = default )
25
28
{
@@ -49,7 +52,7 @@ public async Task BeforeStartAsync(DistributedApplicationModel appModel, Cancell
49
52
continue ;
50
53
}
51
54
52
- var containerApp = await containerAppEnvironmentContext . CreateContainerAppAsync ( r , executionContext , cancellationToken ) . ConfigureAwait ( false ) ;
55
+ var containerApp = await containerAppEnvironmentContext . CreateContainerAppAsync ( r , provisioningOptions . Value , executionContext , cancellationToken ) . ConfigureAwait ( false ) ;
53
56
54
57
r . Annotations . Add ( new DeploymentTargetAnnotation ( containerApp ) ) ;
55
58
}
@@ -75,11 +78,12 @@ IManifestExpressionProvider clientId
75
78
76
79
private readonly Dictionary < IResource , ContainerAppContext > _containerApps = [ ] ;
77
80
78
- public async Task < AzureBicepResource > CreateContainerAppAsync ( IResource resource , DistributedApplicationExecutionContext executionContext , CancellationToken cancellationToken )
81
+ public async Task < AzureBicepResource > CreateContainerAppAsync ( IResource resource , AzureProvisioningOptions provisioningOptions , DistributedApplicationExecutionContext executionContext , CancellationToken cancellationToken )
79
82
{
80
83
var context = await ProcessResourceAsync ( resource , executionContext , cancellationToken ) . ConfigureAwait ( false ) ;
81
84
82
85
var provisioningResource = new AzureProvisioningResource ( resource . Name , context . BuildContainerApp ) ;
86
+ provisioningResource . ProvisioningBuildOptions = provisioningOptions . ProvisioningBuildOptions ;
83
87
84
88
provisioningResource . Annotations . Add ( new ManifestPublishingCallbackAnnotation ( provisioningResource . WriteToManifest ) ) ;
85
89
@@ -143,7 +147,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c)
143
147
containerImageParam = AllocateContainerImageParameter ( ) ;
144
148
}
145
149
146
- var containerAppResource = new ContainerApp ( Infrastructure . NormalizeIdentifierName ( resource . Name ) )
150
+ var containerAppResource = new ContainerApp ( Infrastructure . NormalizeBicepIdentifier ( resource . Name ) )
147
151
{
148
152
Name = resource . Name . ToLowerInvariant ( )
149
153
} ;
@@ -180,7 +184,7 @@ public void BuildContainerApp(AzureResourceInfrastructure c)
180
184
var containerAppContainer = new ContainerAppContainer ( ) ;
181
185
template . Containers = [ containerAppContainer ] ;
182
186
183
- containerAppContainer . Image = containerImageParam is null ? containerImageName : containerImageParam ;
187
+ containerAppContainer . Image = containerImageParam is null ? containerImageName ! : containerImageParam ;
184
188
containerAppContainer . Name = resource . Name ;
185
189
186
190
AddEnvironmentVariablesAndCommandLineArgs ( containerAppContainer ) ;
@@ -497,7 +501,8 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
497
501
{
498
502
var managedIdentityParameter = AllocateManagedIdentityIdParameter ( ) ;
499
503
secret . Identity = managedIdentityParameter ;
500
- secret . KeyVaultUri = new BicepValue < Uri > ( argValue . Expression ! ) ;
504
+ // TODO: this should be able to use ToUri(), but it hit an issue
505
+ secret . KeyVaultUri = new BicepValue < Uri > ( ( ( BicepExpression ? ) argValue ) ! ) ;
501
506
}
502
507
else
503
508
{
@@ -531,7 +536,6 @@ private static BicepValue<string> ResolveValue(object val)
531
536
{
532
537
BicepValue < string > s => s ,
533
538
string s => s ,
534
- BicepValueFormattableString fs => Interpolate ( fs ) ,
535
539
ProvisioningParameter p => p ,
536
540
_ => throw new NotSupportedException ( "Unsupported value type " + val . GetType ( ) )
537
541
} ;
@@ -698,7 +702,7 @@ BicepValue<string> GetHostValue(string? prefix = null, string? suffix = null)
698
702
args [ index ++ ] = val ;
699
703
}
700
704
701
- return ( new BicepValueFormattableString ( expr . Format , args ) , finalSecretType ) ;
705
+ return ( Interpolate ( expr . Format , args ) , finalSecretType ) ;
702
706
703
707
}
704
708
@@ -714,7 +718,7 @@ private BicepValue<string> AllocateKeyVaultSecretUriReference(BicepSecretOutputR
714
718
{
715
719
// We resolve the keyvault that represents the storage for secret outputs
716
720
var parameter = AllocateParameter ( SecretOutputExpression . GetSecretOutputKeyVault ( secretOutputReference . Resource ) ) ;
717
- kv = KeyVaultService . FromExisting ( $ "{ parameter . IdentifierName } _kv") ;
721
+ kv = KeyVaultService . FromExisting ( $ "{ parameter . BicepIdentifier } _kv") ;
718
722
kv . Name = parameter ;
719
723
720
724
KeyVaultRefs [ secretOutputReference . Resource . Name ] = kv ;
@@ -723,19 +727,15 @@ private BicepValue<string> AllocateKeyVaultSecretUriReference(BicepSecretOutputR
723
727
if ( ! KeyVaultSecretRefs . TryGetValue ( secretOutputReference . ValueExpression , out var secret ) )
724
728
{
725
729
// Now we resolve the secret
726
- var secretIdentifierName = Infrastructure . NormalizeIdentifierName ( $ "{ kv . IdentifierName } _{ secretOutputReference . Name } ") ;
727
- secret = KeyVaultSecret . FromExisting ( secretIdentifierName ) ;
730
+ var secretBicepIdentifier = Infrastructure . NormalizeBicepIdentifier ( $ "{ kv . BicepIdentifier } _{ secretOutputReference . Name } ") ;
731
+ secret = KeyVaultSecret . FromExisting ( secretBicepIdentifier ) ;
728
732
secret . Name = secretOutputReference . Name ;
729
733
secret . Parent = kv ;
730
734
731
735
KeyVaultSecretRefs [ secretOutputReference . ValueExpression ] = secret ;
732
736
}
733
737
734
- // TODO: There should be a better way to do this?
735
- return new MemberExpression (
736
- new MemberExpression (
737
- new IdentifierExpression ( secret . IdentifierName ) , "properties" ) ,
738
- "secretUri" ) ;
738
+ return secret . Properties . SecretUri ;
739
739
}
740
740
741
741
private ProvisioningParameter AllocateContainerImageParameter ( )
@@ -895,81 +895,45 @@ private void AddContainerRegistryParameters(ContainerAppConfiguration app)
895
895
}
896
896
}
897
897
898
- // REVIEW: BicepFunction.Interpolate is buggy and doesn't handle nested formattable strings correctly
899
- // This is a workaround to handle nested formattable strings until the bug is fixed.
900
- private static BicepValue < string > Interpolate ( BicepValueFormattableString text )
898
+ private static BicepValue < string > Interpolate ( string format , object [ ] args )
901
899
{
902
- var formatStringBuilder = new StringBuilder ( ) ;
903
- var arguments = new List < BicepValue < string > > ( ) ;
900
+ var bicepStringBuilder = new BicepStringBuilder ( ) ;
904
901
905
- void ProcessFormattableString ( BicepValueFormattableString formattableString , int argumentIndex )
906
- {
907
- var span = formattableString . Format . AsSpan ( ) ;
908
- var skip = 0 ;
909
-
910
- foreach ( var match in Regex . EnumerateMatches ( span , @"{\d+}" ) )
911
- {
912
- formatStringBuilder . Append ( span [ ..( match . Index - skip ) ] ) ;
902
+ var span = format . AsSpan ( ) ;
903
+ var skip = 0 ;
904
+ var argumentIndex = 0 ;
913
905
914
- var argument = formattableString . GetArgument ( argumentIndex ) ;
906
+ foreach ( var match in Regex . EnumerateMatches ( span , @"{\d+}" ) )
907
+ {
908
+ bicepStringBuilder . Append ( span [ ..( match . Index - skip ) ] . ToString ( ) ) ;
915
909
916
- if ( argument is BicepValueFormattableString nested )
917
- {
918
- // Inline the nested formattable string
919
- ProcessFormattableString ( nested , 0 ) ;
920
- }
921
- else
922
- {
923
- formatStringBuilder . Append ( CultureInfo . InvariantCulture , $ "{{{arguments.Count}}}") ;
924
- if ( argument is BicepValue < string > bicepValue )
925
- {
926
- arguments . Add ( bicepValue ) ;
927
- }
928
- else if ( argument is string s )
929
- {
930
- arguments . Add ( s ) ;
931
- }
932
- else if ( argument is ProvisioningParameter provisioningParameter )
933
- {
934
- arguments . Add ( provisioningParameter ) ;
935
- }
936
- else
937
- {
938
- throw new NotSupportedException ( $ "{ argument } is not supported") ;
939
- }
940
- }
910
+ var argument = args [ argumentIndex ] ;
941
911
942
- argumentIndex ++ ;
943
- span = span [ ( match . Index + match . Length - skip ) ..] ;
944
- skip = match . Index + match . Length ;
912
+ if ( argument is BicepValue < string > bicepValue )
913
+ {
914
+ bicepStringBuilder . Append ( $ "{ bicepValue } ") ;
915
+ }
916
+ else if ( argument is string s )
917
+ {
918
+ bicepStringBuilder . Append ( s ) ;
919
+ }
920
+ else if ( argument is ProvisioningParameter provisioningParameter )
921
+ {
922
+ bicepStringBuilder . Append ( $ "{ provisioningParameter } ") ;
923
+ }
924
+ else
925
+ {
926
+ throw new NotSupportedException ( $ "{ argument } is not supported") ;
945
927
}
946
928
947
- formatStringBuilder . Append ( span ) ;
948
- }
949
-
950
- ProcessFormattableString ( text , 0 ) ;
951
-
952
- var formatString = formatStringBuilder . ToString ( ) ;
953
-
954
- if ( formatString == "{0}" )
955
- {
956
- return arguments [ 0 ] ;
929
+ argumentIndex ++ ;
930
+ span = span [ ( match . Index + match . Length - skip ) ..] ;
931
+ skip = match . Index + match . Length ;
957
932
}
958
933
959
- return BicepFunction . Interpolate ( new BicepValueFormattableString ( formatString , [ .. arguments ] ) ) ;
960
- }
934
+ bicepStringBuilder . Append ( span . ToString ( ) ) ;
961
935
962
- /// <summary>
963
- /// A custom FormattableString implementation that allows us to inline nested formattable strings.
964
- /// </summary>
965
- private sealed class BicepValueFormattableString ( string formatString , object [ ] values ) : FormattableString
966
- {
967
- public override int ArgumentCount => values . Length ;
968
- public override string Format => formatString ;
969
- public override object ? GetArgument ( int index ) => values [ index ] ;
970
- public override object ? [ ] GetArguments ( ) => values ;
971
- public override string ToString ( IFormatProvider ? formatProvider ) => Format ;
972
- public override string ToString ( ) => formatString ;
936
+ return bicepStringBuilder . Build ( ) ;
973
937
}
974
938
975
939
/// <summary>
0 commit comments