diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props
index d4c80facc4c71..ac1cdfd2abb87 100644
--- a/eng/Packages.Data.props
+++ b/eng/Packages.Data.props
@@ -128,7 +128,7 @@
-
+
@@ -158,6 +158,7 @@
+
diff --git a/sdk/cloudmachine/Azure.CloudMachine/src/CloudMachineWorkspace.cs b/sdk/cloudmachine/Azure.CloudMachine/src/CloudMachineWorkspace.cs
index ee7d352493d6c..a0f8ecf1acb61 100644
--- a/sdk/cloudmachine/Azure.CloudMachine/src/CloudMachineWorkspace.cs
+++ b/sdk/cloudmachine/Azure.CloudMachine/src/CloudMachineWorkspace.cs
@@ -18,10 +18,7 @@ namespace Azure.CloudMachine;
///
public class CloudMachineWorkspace : ClientWorkspace
{
- private TokenCredential Credential { get; } = new ChainedTokenCredential(
- new AzureCliCredential(),
- new AzureDeveloperCliCredential()
- );
+ private TokenCredential Credential { get; }
///
/// The cloud machine ID.
@@ -42,20 +39,21 @@ public CloudMachineWorkspace(TokenCredential credential = default, IConfiguratio
{
Credential = credential;
}
-
- string cmid;
- if (configuration == default)
- {
- cmid = ReadOrCreateCmid();
- }
else
{
- cmid = configuration["CloudMachine:ID"];
- if (cmid == null)
- throw new Exception("CloudMachine:ID configuration value missing");
+ // This environment variable is set by the CloudMachine App Service feature during provisioning.
+ Credential = Environment.GetEnvironmentVariable("CLOUDMACHINE_MANAGED_IDENTITY_CLIENT_ID") switch
+ {
+ string clientId when !string.IsNullOrEmpty(clientId) => new ManagedIdentityCredential(clientId),
+ _ => new ChainedTokenCredential(new AzureCliCredential(), new AzureDeveloperCliCredential())
+ };
}
- Id = cmid!;
+ Id = configuration switch
+ {
+ null => ReadOrCreateCmid(),
+ _ => configuration["CloudMachine:ID"] ?? throw new Exception("CloudMachine:ID configuration value missing")
+ };
}
///
@@ -69,7 +67,8 @@ public CloudMachineWorkspace(TokenCredential credential = default, IConfiguratio
public override ClientConnectionOptions GetConnectionOptions(Type clientType, string instanceId)
{
string clientId = clientType.FullName;
- if (instanceId != null && instanceId.StartsWith("$")) clientId = $"{clientType.FullName}{instanceId}";
+ if (instanceId != null && instanceId.StartsWith("$"))
+ clientId = $"{clientType.FullName}{instanceId}";
switch (clientId)
{
@@ -78,13 +77,13 @@ public override ClientConnectionOptions GetConnectionOptions(Type clientType, st
case "Azure.Messaging.ServiceBus.ServiceBusClient":
return new ClientConnectionOptions(new($"https://{Id}.servicebus.windows.net"), Credential);
case "Azure.Messaging.ServiceBus.ServiceBusSender":
- return new ClientConnectionOptions(instanceId?? "cm_servicebus_default_topic");
+ return new ClientConnectionOptions(instanceId ?? "cm_servicebus_default_topic");
case "Azure.Messaging.ServiceBus.ServiceBusProcessor":
return new ClientConnectionOptions("cm_servicebus_default_topic/cm_servicebus_subscription_default");
case "Azure.Messaging.ServiceBus.ServiceBusProcessor$private":
return new ClientConnectionOptions("cm_servicebus_topic_private/cm_servicebus_subscription_private");
case "Azure.Storage.Blobs.BlobContainerClient":
- return new ClientConnectionOptions(new($"https://{Id}.blob.core.windows.net/{instanceId??"default"}"), Credential);
+ return new ClientConnectionOptions(new($"https://{Id}.blob.core.windows.net/{instanceId ?? "default"}"), Credential);
case "Azure.AI.OpenAI.AzureOpenAIClient":
return new ClientConnectionOptions(new($"https://{Id}.openai.azure.com"), Credential);
case "OpenAI.Chat.ChatClient":
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.net8.0.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.net8.0.cs
index 02b7cef4a0ba6..0bd82024d3767 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.net8.0.cs
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.net8.0.cs
@@ -46,6 +46,15 @@ public FeatureCollection() { }
public System.Collections.Generic.IEnumerable FindAll() where T : Azure.Provisioning.CloudMachine.CloudMachineFeature { throw null; }
}
}
+namespace Azure.CloudMachine.AppService
+{
+ public partial class AppServiceFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature
+ {
+ public AppServiceFeature(Azure.Provisioning.AppService.AppServiceSkuDescription? sku = null) { }
+ public Azure.Provisioning.AppService.AppServiceSkuDescription Sku { get { throw null; } set { } }
+ protected override Azure.Provisioning.Primitives.ProvisionableResource EmitCore(Azure.CloudMachine.CloudMachineInfrastructure infrastructure) { throw null; }
+ }
+}
namespace Azure.CloudMachine.KeyVault
{
public partial class KeyVaultFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs
index 02b7cef4a0ba6..0bd82024d3767 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/api/Azure.Provisioning.CloudMachine.netstandard2.0.cs
@@ -46,6 +46,15 @@ public FeatureCollection() { }
public System.Collections.Generic.IEnumerable FindAll() where T : Azure.Provisioning.CloudMachine.CloudMachineFeature { throw null; }
}
}
+namespace Azure.CloudMachine.AppService
+{
+ public partial class AppServiceFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature
+ {
+ public AppServiceFeature(Azure.Provisioning.AppService.AppServiceSkuDescription? sku = null) { }
+ public Azure.Provisioning.AppService.AppServiceSkuDescription Sku { get { throw null; } set { } }
+ protected override Azure.Provisioning.Primitives.ProvisionableResource EmitCore(Azure.CloudMachine.CloudMachineInfrastructure infrastructure) { throw null; }
+ }
+}
namespace Azure.CloudMachine.KeyVault
{
public partial class KeyVaultFeature : Azure.Provisioning.CloudMachine.CloudMachineFeature
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj
index 791ca221494cb..ff39f04075f17 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/Azure.Provisioning.CloudMachine.csproj
@@ -18,6 +18,7 @@
+
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/AppServiceFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/AppServiceFeature.cs
new file mode 100644
index 0000000000000..4b72fc998eeaa
--- /dev/null
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/AppServiceFeature.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Provisioning.CloudMachine;
+using Azure.Provisioning.Expressions;
+using Azure.Provisioning.AppService;
+using Azure.Provisioning.Primitives;
+using Azure.Provisioning.Resources;
+
+namespace Azure.CloudMachine.AppService;
+
+public class AppServiceFeature : CloudMachineFeature
+{
+ public AppServiceSkuDescription Sku { get; set; }
+
+ public AppServiceFeature(AppServiceSkuDescription? sku = default)
+ {
+ if (sku == default)
+ {
+ sku = new AppServiceSkuDescription { Tier = "Free", Name = "F1" };
+ }
+ Sku = sku;
+ }
+
+ protected override ProvisionableResource EmitCore(CloudMachineInfrastructure infrastructure)
+ {
+ //Add a App Service to the CloudMachine infrastructure.
+ AppServicePlan hostingPlan = new("cm_hosting_plan")
+ {
+ Name = infrastructure.Id,
+ Sku = Sku,
+ Kind = "app"
+ };
+ infrastructure.AddResource(hostingPlan);
+
+ WebSite appService = new("cm_website")
+ {
+ Name = infrastructure.Id,
+ Kind = "app",
+ Tags = { { "azd-service-name", infrastructure.Id } },
+ Identity = new()
+ {
+ ManagedServiceIdentityType = ManagedServiceIdentityType.UserAssigned,
+ UserAssignedIdentities = { { BicepFunction.Interpolate($"{infrastructure.Identity.Id}").Compile().ToString(), new UserAssignedIdentityDetails() } }
+ },
+ AppServicePlanId = hostingPlan.Id,
+ IsHttpsOnly = true,
+ IsEnabled = true,
+ SiteConfig = new()
+ {
+ IsHttp20Enabled = true,
+ MinTlsVersion = AppServiceSupportedTlsVersion.Tls1_2,
+ IsWebSocketsEnabled = true,
+ AppSettings = new()
+ {
+ // This is used by the CloudMachineWorkspace to detect that it is running in a deployed App Service.
+ // The ClientId is used to create a ManagedIdentityCredential so that it wires up to our CloudMachine user-assigned identity.
+ new AppServiceNameValuePair
+ {
+ Name = "CLOUDMACHINE_MANAGED_IDENTITY_CLIENT_ID",
+ Value = infrastructure.Identity.ClientId
+ },
+ }
+ }
+ };
+ infrastructure.AddResource(appService);
+
+ return appService;
+ }
+}
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs
index e5ca6413d0418..2b925548b9562 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/AzureSdkExtensions/OpenAIFeature.cs
@@ -27,6 +27,13 @@ protected override ProvisionableResource EmitCore(CloudMachineInfrastructure clo
cloudMachine.PrincipalIdParameter)
);
+ cloudMachine.AddResource(cloudMachine.CreateRoleAssignment(
+ cognitiveServices,
+ cognitiveServices.Id,
+ CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor,
+ cloudMachine.Identity)
+ );
+
Emitted = cognitiveServices;
OpenAIModel? previous = null;
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs
index a442a905b2e5b..e883d9defd022 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/src/CDKLevel3/CloudMachineInfrastructure.cs
@@ -13,6 +13,8 @@
using System.Collections.Generic;
using Azure.Provisioning;
using Azure.Provisioning.CloudMachine;
+using Azure.Core;
+using System.Runtime.CompilerServices;
namespace Azure.CloudMachine;
@@ -221,7 +223,8 @@ public void AddFeature(CloudMachineFeature feature)
public void AddEndpoints()
{
Type endpointsType = typeof(T);
- if (!endpointsType.IsInterface) throw new InvalidOperationException("Endpoints type must be an interface.");
+ if (!endpointsType.IsInterface)
+ throw new InvalidOperationException("Endpoints type must be an interface.");
Endpoints.Add(endpointsType);
}
@@ -242,33 +245,35 @@ public ProvisioningPlan Build(ProvisioningBuildOptions? context = null)
//Add(PrincipalTypeParameter);
//Add(PrincipalNameParameter);
+ var storageBlobDataContributor = StorageBuiltInRole.StorageBlobDataContributor;
+ var storageTableDataContributor = StorageBuiltInRole.StorageTableDataContributor;
+ var azureServiceBusDataSender = ServiceBusBuiltInRole.AzureServiceBusDataSender;
+ var azureServiceBusDataOwner = ServiceBusBuiltInRole.AzureServiceBusDataOwner;
+
_infrastructure.Add(Identity);
_infrastructure.Add(_storage);
- _infrastructure.Add(_storage.CreateRoleAssignment(StorageBuiltInRole.StorageBlobDataContributor, RoleManagementPrincipalType.User, PrincipalIdParameter));
- _infrastructure.Add(_storage.CreateRoleAssignment(StorageBuiltInRole.StorageTableDataContributor, RoleManagementPrincipalType.User, PrincipalIdParameter));
+ _infrastructure.Add(_storage.CreateRoleAssignment(storageBlobDataContributor, RoleManagementPrincipalType.User, PrincipalIdParameter));
+ _infrastructure.Add(CreateRoleAssignment(_storage, _storage.Id, storageBlobDataContributor, Identity));
+ _infrastructure.Add(_storage.CreateRoleAssignment(storageTableDataContributor, RoleManagementPrincipalType.User, PrincipalIdParameter));
+ _infrastructure.Add(CreateRoleAssignment(_storage, _storage.Id, storageTableDataContributor, Identity));
_infrastructure.Add(_container);
_infrastructure.Add(_blobs);
_infrastructure.Add(_serviceBusNamespace);
- _infrastructure.Add(_serviceBusNamespace.CreateRoleAssignment(ServiceBusBuiltInRole.AzureServiceBusDataOwner, RoleManagementPrincipalType.User, PrincipalIdParameter));
+ _infrastructure.Add(_serviceBusNamespace.CreateRoleAssignment(azureServiceBusDataOwner, RoleManagementPrincipalType.User, PrincipalIdParameter));
+ _infrastructure.Add(CreateRoleAssignment(_serviceBusNamespace,_serviceBusNamespace.Id, azureServiceBusDataOwner, Identity));
_infrastructure.Add(_serviceBusNamespaceAuthorizationRule);
_infrastructure.Add(_serviceBusTopic_private);
_infrastructure.Add(_serviceBusTopic_default);
_infrastructure.Add(_serviceBusSubscription_private);
_infrastructure.Add(_serviceBusSubscription_default);
- // This is necessary until SystemTopic adds an AssignRole method.
- var role = ServiceBusBuiltInRole.AzureServiceBusDataSender;
- RoleAssignment roleAssignment = new RoleAssignment("cm_servicebus_role");
- roleAssignment.Name = BicepFunction.CreateGuid(_serviceBusNamespace.Id, Identity.Id, BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role.ToString()));
- roleAssignment.Scope = new IdentifierExpression(_serviceBusNamespace.BicepIdentifier);
- roleAssignment.PrincipalType = RoleManagementPrincipalType.ServicePrincipal;
- roleAssignment.RoleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role.ToString());
- roleAssignment.PrincipalId = Identity.PrincipalId;
+ RoleAssignment roleAssignment = CreateRoleAssignment(_serviceBusNamespace, _serviceBusNamespace.Id, azureServiceBusDataSender, Identity);
_infrastructure.Add(roleAssignment);
+
+ CreateRoleAssignment(_serviceBusNamespace, _serviceBusNamespace.Id, azureServiceBusDataSender, Identity);
// the role assignment must exist before the system topic event subscription is created.
_eventGridSubscription_blobs.DependsOn.Add(roleAssignment);
_infrastructure.Add(_eventGridSubscription_blobs);
-
_infrastructure.Add(_eventGridTopic_blobs);
// Placeholders for now.
@@ -283,4 +288,21 @@ public ProvisioningPlan Build(ProvisioningBuildOptions? context = null)
return _infrastructure.Build(context);
}
+
+ // Temporary until the bug is fixed in the CDK generator which uses the PrincipalId instead of the Id in BicepFunction.CreateGuid.
+ internal RoleAssignment CreateRoleAssignment(ProvisionableResource resource, BicepValue Id, object role, UserAssignedIdentity identity)
+ {
+ if (role is null) throw new ArgumentException("Role must not be null.", nameof(role));
+ var method = role.GetType().GetMethod("GetBuiltInRoleName", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+ string roleName = (string)method!.Invoke(null, [role])!;
+
+ return new($"{resource.BicepIdentifier}_{identity.BicepIdentifier}_{roleName}")
+ {
+ Name = BicepFunction.CreateGuid(Id, identity.Id, BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role!.ToString()!)),
+ Scope = new IdentifierExpression(resource.BicepIdentifier),
+ PrincipalType = RoleManagementPrincipalType.ServicePrincipal,
+ RoleDefinitionId = BicepFunction.GetSubscriptionResourceId("Microsoft.Authorization/roleDefinitions", role.ToString()!),
+ PrincipalId = identity.PrincipalId
+ };
+ }
}
diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs b/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs
index ee4fc0edfe1d8..cd87f20d7fe99 100644
--- a/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs
+++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/tests/CloudMachineTests.cs
@@ -3,6 +3,7 @@
#nullable enable
+using Azure.CloudMachine.AppService;
using Azure.CloudMachine.KeyVault;
using Azure.CloudMachine.OpenAI;
using NUnit.Framework;
@@ -19,6 +20,7 @@ public void GenerateBicep()
infrastructure.AddFeature(new KeyVaultFeature());
infrastructure.AddFeature(new OpenAIModel("gpt-35-turbo", "0125"));
infrastructure.AddFeature(new OpenAIModel("text-embedding-ada-002", "2", AIModelKind.Embedding));
+ infrastructure.AddFeature(new AppServiceFeature());
}, exitProcessIfHandled:false);
}