Skip to content

Commit

Permalink
Use RBAC and bicep provisioning for Azure OpenAI
Browse files Browse the repository at this point in the history
Fixes #2490
  • Loading branch information
sebastienros committed Feb 29, 2024
1 parent c67d3c4 commit 73a0715
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

<ItemGroup>
<ProjectReference Include="..\..\..\src\Aspire.Dashboard\Aspire.Dashboard.csproj" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\..\src\Aspire.Hosting.Azure.Provisioning\Aspire.Hosting.Azure.Provisioning.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\OpenAIEndToEnd.WebStory\OpenAIEndToEnd.WebStory.csproj" />
</ItemGroup>

Expand Down
4 changes: 3 additions & 1 deletion playground/OpenAIEndToEnd/OpenAIEndToEnd.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

var builder = DistributedApplication.CreateBuilder(args);

var openai = builder.AddConnectionString("openai");
builder.AddAzureProvisioning();

var openai = builder.AddAzureOpenAI("openai");

builder.AddProject<Projects.OpenAIEndToEnd_WebStory>("webstory")
.WithReference(openai);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"resources": {
"openai": {
"type": "parameter.v0",
"connectionString": "{openai.value}",
"value": "{openai.inputs.value}",
"inputs": {
"value": {
"type": "string",
"secret": true
}
"type": "azure.bicep.v0",
"connectionString": "{openai.outputs.connectionString}",
"path": "aspire.hosting.azure.bicep.openai.bicep",
"params": {
"name": "openai",
"deployments": [],
"principalId": "",
"principalType": ""
}
},
"webstory": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// c.f., https://learn.microsoft.com/azure/ai-services/create-account-bicep

param name string
param principalId string
param principalType string = 'ServicePrincipal'
param deployments array = [] // This is a placeholder. Deployments provisioning is not supported yet.

@description('Tags that will be applied to all resources')
param tags object = {}

@description('Location for all resources.')
param location string = resourceGroup().location

@allowed([
'S0'
])
param sku string = 'S0'

var resourceToken = uniqueString(resourceGroup().id)

resource cognitiveService 'Microsoft.CognitiveServices/accounts@2021-10-01' = {
name: '${name}-${resourceToken}'
location: location
sku: {
name: sku
}
kind: 'OpenAI'
properties: {
apiProperties: {
statisticsEnabled: false
}
}
tags: tags
}

// Find list of roles and GUIDs in https://learn.microsoft.com/azure/role-based-access-control/built-in-roles

// Cognitive Services OpenAI Contributor
var contributorRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')
resource cognitiveServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(cognitiveService.id, principalId, contributorRole)
scope: cognitiveService
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: contributorRole
}
}

output connectionString string = 'Endpoint=${cognitiveService.properties.endpoint}'
21 changes: 15 additions & 6 deletions src/Aspire.Hosting.Azure/AzureOpenAIResource.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.Azure;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents an Azure OpenAI resource.
/// </summary>
/// <param name="name">The name of the resource.</param>
public class AzureOpenAIResource(string name) : Resource(name), IAzureResource, IResourceWithConnectionString
public class AzureOpenAIResource(string name) :
AzureBicepResource(name, templateResourceName: "Aspire.Hosting.Azure.Bicep.openai.bicep"),
IResourceWithConnectionString
{
private readonly List<AzureOpenAIDeploymentResource> _deployments = [];

/// <summary>
/// Gets or sets the connection string for the Azure OpenAI resource.
/// Gets the "connectionString" output reference from the Azure OpenAI resource.
/// </summary>
public BicepOutputReference ConnectionString => new("connectionString", this);

/// <summary>
/// Gets the connection string template for the manifest for the resource.
/// </summary>
public string? ConnectionString { get; set; }
public string ConnectionStringExpression => ConnectionString.ValueExpression;

/// <summary>
/// Gets the connection string for the Azure OpenAI resource.
/// Gets the connection string for the resource.
/// </summary>
/// <returns>The connection string for the Azure OpenAI resource.</returns>
string? IResourceWithConnectionString.GetConnectionString() => ConnectionString;
/// <returns>The connection string for the resource.</returns>
public string? GetConnectionString() => ConnectionString.Value;

/// <summary>
/// Gets the list of deployments of the Azure OpenAI resource.
Expand Down
42 changes: 0 additions & 42 deletions src/Aspire.Hosting.Azure/AzureResourceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Publishing;

namespace Aspire.Hosting;

Expand All @@ -11,47 +10,6 @@ namespace Aspire.Hosting;
/// </summary>
public static class AzureResourceExtensions
{
/// <summary>
/// Adds an Azure OpenAI resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureOpenAIResource}"/>.</returns>
public static IResourceBuilder<AzureOpenAIResource> AddAzureOpenAI(this IDistributedApplicationBuilder builder, string name)
{
var resource = new AzureOpenAIResource(name);
return builder.AddResource(resource)
.WithManifestPublishingCallback(WriteAzureOpenAIToManifest);
}

private static void WriteAzureOpenAIToManifest(ManifestPublishingContext context)
{
context.Writer.WriteString("type", "azure.openai.account.v0");
}

/// <summary>
/// Adds an Azure OpenAI Deployment resource to the application model. This resource requires an <see cref="AzureOpenAIResource"/> to be added to the application model.
/// </summary>
/// <param name="serverBuilder">The Azure SQL Server resource builder.</param>
/// <param name="name">The name of the deployment.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureSqlDatabaseResource}"/>.</returns>
public static IResourceBuilder<AzureOpenAIDeploymentResource> AddDeployment(this IResourceBuilder<AzureOpenAIResource> serverBuilder, string name)
{
var resource = new AzureOpenAIDeploymentResource(name, serverBuilder.Resource);
return serverBuilder.ApplicationBuilder.AddResource(resource)
.WithManifestPublishingCallback(context => WriteAzureOpenAIDeploymentToManifest(context, resource));
}

private static void WriteAzureOpenAIDeploymentToManifest(ManifestPublishingContext context, AzureOpenAIDeploymentResource resource)
{
// Example:
// "type": "azure.openai.deployment.v0",
// "parent": "azureOpenAi",

context.Writer.WriteString("type", "azure.openai.deployment.v0");
context.Writer.WriteString("parent", resource.Parent.Name);
}

/// <summary>
/// Changes the resource to be published as a connection string reference in the manifest.
/// </summary>
Expand Down
50 changes: 50 additions & 0 deletions src/Aspire.Hosting.Azure/Bicep/openai.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// c.f., https://learn.microsoft.com/azure/ai-services/create-account-bicep

param name string
param principalId string
param principalType string = 'ServicePrincipal'
param deployments array = [] // This is a placeholder. Deployments provisioning is not supported yet.

@description('Tags that will be applied to all resources')
param tags object = {}

@description('Location for all resources.')
param location string = resourceGroup().location

@allowed([
'S0'
])
param sku string = 'S0'

var resourceToken = uniqueString(resourceGroup().id)

resource cognitiveService 'Microsoft.CognitiveServices/accounts@2021-10-01' = {
name: '${name}-${resourceToken}'
location: location
sku: {
name: sku
}
kind: 'OpenAI'
properties: {
apiProperties: {
statisticsEnabled: false
}
}
tags: tags
}

// Find list of roles and GUIDs in https://learn.microsoft.com/azure/role-based-access-control/built-in-roles

// Cognitive Services OpenAI Contributor
var contributorRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')
resource cognitiveServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(cognitiveService.id, principalId, contributorRole)
scope: cognitiveService
properties: {
principalId: principalId
principalType: principalType
roleDefinitionId: contributorRole
}
}

output connectionString string = 'Endpoint=${cognitiveService.properties.endpoint}'
42 changes: 42 additions & 0 deletions src/Aspire.Hosting.Azure/Extensions/AzureOpenAIExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for adding the Azure OpenAI resources to the application model.
/// </summary>
public static class AzureOpenAIExtensions
{
/// <summary>
/// Adds an Azure OpenAI resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureOpenAIResource}"/>.</returns>
public static IResourceBuilder<AzureOpenAIResource> AddAzureOpenAI(this IDistributedApplicationBuilder builder, string name)
{
var resource = new AzureOpenAIResource(name);
return builder.AddResource(resource)
.WithParameter("name", resource.CreateBicepResourceName())
.WithParameter("deployments", resource.Deployments.Select(x => x.Name))
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalType)
.WithManifestPublishingCallback(resource.WriteToManifest);
}

/// <summary>
/// Adds an Azure OpenAI Deployment resource to the application model. This resource requires an <see cref="AzureOpenAIResource"/> to be added to the application model.
/// </summary>
/// <param name="serverBuilder">The Azure SQL Server resource builder.</param>
/// <param name="name">The name of the deployment.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{AzureSqlDatabaseResource}"/>.</returns>
public static IResourceBuilder<AzureOpenAIDeploymentResource> AddDeployment(this IResourceBuilder<AzureOpenAIResource> serverBuilder, string name)
{
var resource = new AzureOpenAIDeploymentResource(name, serverBuilder.Resource);
return serverBuilder.ApplicationBuilder.AddResource(resource);
}
}
20 changes: 20 additions & 0 deletions tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,4 +444,24 @@ public void PublishAsConnectionString()
Assert.Equal("{ai.connectionString}", serviceManifest["env"]?["APPLICATIONINSIGHTS_CONNECTION_STRING"]?.ToString());
Assert.Equal("{servicebus.connectionString}", serviceManifest["env"]?["ConnectionStrings__servicebus"]?.ToString());
}

[Fact]
public void AddAzureOpenAI()
{
var builder = DistributedApplication.CreateBuilder();

var openai = builder.AddAzureOpenAI("openai");
openai.AddDeployment("mymodel");

openai.Resource.Outputs["connectionString"] = "myopenaiconnectionstring";

var deployments = openai.Resource.Parameters["deployments"] as IEnumerable<string>;

Assert.Equal("Aspire.Hosting.Azure.Bicep.openai.bicep", openai.Resource.TemplateResourceName);
Assert.Equal("openai", openai.Resource.Name);
Assert.NotNull(deployments);
Assert.Equal(["mymodel"], deployments);
Assert.Equal("myopenaiconnectionstring", openai.Resource.GetConnectionString());
Assert.Equal("{openai.outputs.connectionString}", openai.Resource.ConnectionStringExpression);
}
}

0 comments on commit 73a0715

Please sign in to comment.