Skip to content

Commit

Permalink
Merge branch 'aliencube:main' into feature/22-request-payload-definition
Browse files Browse the repository at this point in the history
  • Loading branch information
jihyunmoon16 authored Oct 12, 2024
2 parents ad5475f + 97a3b53 commit 95906e5
Show file tree
Hide file tree
Showing 86 changed files with 4,614 additions and 1,038 deletions.
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Auto detect text files and perform LF normalization
* text=auto

*.sln text eol=crlf
*.csproj text eol=crlf
*.props text eol=crlf
*.sh text eol=lf
6 changes: 4 additions & 2 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ jobs:
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
AZURE_OPENAI_KEYS: ${{ secrets.AZURE_OPENAI_KEYS }}
AZURE_KEYVAULT_URI: ${{ secrets.AZURE_KEYVAULT_URI }}
AZURE_KEYVAULT_SECRET_NAME: ${{ vars.AZURE_KEYVAULT_SECRET_NAME }}
AZURE_KEYVAULT_SECRET_NAME_OPENAI: ${{ vars.AZURE_KEYVAULT_SECRET_NAME_OPENAI }}
AZURE_KEYVAULT_SECRET_NAME_STORAGE: ${{ vars.AZURE_KEYVAULT_SECRET_NAME_STORAGE }}

steps:
- name: Checkout
Expand Down Expand Up @@ -54,7 +55,8 @@ jobs:
$appsettings = Get-Content -Path "./src/AzureOpenAIProxy.ApiApp/appsettings.json" -Raw | ConvertFrom-Json
$appsettings.Azure.OpenAI.Instances = @()
$appsettings.Azure.KeyVault.VaultUri = "${{ env.AZURE_KEYVAULT_URI }}"
$appsettings.Azure.KeyVault.SecretName = "${{ env.AZURE_KEYVAULT_SECRET_NAME }}"
$appsettings.Azure.KeyVault.SecretNames.OpenAI = "${{ env.AZURE_KEYVAULT_SECRET_NAME_OPENAI }}"
$appsettings.Azure.KeyVault.SecretNames.Storage = "${{ env.AZURE_KEYVAULT_SECRET_NAME_STORAGE }}"
$appsettings | ConvertTo-Json -Depth 100 | Set-Content -Path "./src/AzureOpenAIProxy.ApiApp/appsettings.json" -Encoding UTF8 -Force
- name: Install Spectral Cli
Expand Down
22 changes: 22 additions & 0 deletions .spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,25 @@ rules:
- field: 'content'
function: truthy
message: Content is required

# Ensure endpoints with path variables define a 404 response
# path에 path variable이 있다면 404 응답 코드가 있어야 함
path-variables-require-404:
description: Path variables must include a 404 response
severity: error
given: $.paths[*][*].parameters[?(@.in == 'path')]^^
then:
- field: responses.404
function: truthy
message: Response 404 is required

# Ensure POST, PUT, PATCH methods define a 400 response
# verb가 POST, PUT, PATCH일 경우 400 응답코드가 있어야 함
post-put-patch-require-400:
description: POST, PUT, PATCH methods must include a 400 response
given: $.paths[*][?(@property == 'post' || @property == 'put' || @property == 'patch')]
severity: error
then:
- field: responses.400
function: truthy
message: Response 400 is required
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<AspireVersion>8.2.0</AspireVersion>
<AspireVersion>8.2.1</AspireVersion>
<AzureOpenAIVersion>2.*-*</AzureOpenAIVersion>
<AspNetCoreVersion>8.*</AspNetCoreVersion>
<MicrosoftExtensionsVersion>8.*</MicrosoftExtensionsVersion>
Expand Down
41 changes: 41 additions & 0 deletions infra/aspire.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ param enabledForDeployment bool = true
param enabledForTemplateDeployment bool = true
param enableRbacAuthorization bool = true

//TODO: 배포 시점에 사용자 princpalId, apiapp principalId를 받는 방법 조사
//param creatorAdminPrincipalId string = ''
//param apiAppUserPrincipalId string = ''

// parameters for storage account
param storageAccountName string = ''
// tableNames passed as a comma separated string from command line
param tableNames string = 'events'

var abbrs = loadJsonContent('./abbreviations.json')

// Tags that should be applied to all resources.
Expand All @@ -39,6 +48,9 @@ var resourceToken = uniqueString(resourceGroup().id)
#disable-next-line no-unused-vars
// var apiServiceName = 'python-api'

// tables for storage account seperated by comma
var tables = split(tableNames, ',')

// Add resources to be provisioned below.

// Provision Key Vault
Expand All @@ -54,6 +66,35 @@ module keyVault './core/security/keyvault.bicep' = {
}
}

// Provision Storage Account
module storageAccount './core/storage/storage-account.bicep' = {
name: 'storageAccount'
params: {
name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}'
location: location
tags: tags
tables: tables
keyVaultName: keyVault.outputs.name
}
}

// TODO: Key vault Secret 권한부여, 생성한 사람에게 관리자 권한을, 그 외에는 secret user 권한을 부여
//resource keyVaultSecretRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
// name: guid(resourceGroup().id, resolvedKeyVaultName, 'secret-role-assignment')
// properties: {
// principalId: creatorAdminPrincipalId
// roleDefinitionId: '00482A5A-887F-4FB3-B363-3B7FE8E74483' // administrator role
// }
//}
//
//resource keyVaultSecretApiAppRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
// name: guid(resourceGroup().id, resolvedKeyVaultName, 'secret-apiapp-role-assignment')
// properties: {
// principalId: apiAppUserPrincipalId
// roleDefinitionId: '4633458B-17DE-408A-B874-0445C86B69E6' // secret user role
// }
//}

// Add outputs from the deployment here, if needed.
//
// This allows the outputs to be referenced by other bicep deployments in the deployment pipeline,
Expand Down
118 changes: 118 additions & 0 deletions infra/core/storage/storage-account.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
metadata description = 'Creates an Azure storage account.'
param name string
param location string = resourceGroup().location
param tags object = {}

@allowed([
'Cool'
'Hot'
'Premium' ])
param accessTier string = 'Hot'
param allowBlobPublicAccess bool = true
param allowCrossTenantReplication bool = true
param allowSharedKeyAccess bool = true
param containers array = []
param corsRules array = []
param defaultToOAuthAuthentication bool = false
param deleteRetentionPolicy object = {}
@allowed([ 'AzureDnsZone', 'Standard' ])
param dnsEndpointType string = 'Standard'
param files array = []
param kind string = 'StorageV2'
param minimumTlsVersion string = 'TLS1_2'
param queues array = []
param shareDeleteRetentionPolicy object = {}
param supportsHttpsTrafficOnly bool = true
param tables array = []
param networkAcls object = {
bypass: 'AzureServices'
defaultAction: 'Allow'
}
@allowed([ 'Enabled', 'Disabled' ])
param publicNetworkAccess string = 'Enabled'
param sku object = { name: 'Standard_LRS' }
param keyVaultName string = ''

resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: name
location: location
tags: tags
kind: kind
sku: sku
properties: {
accessTier: accessTier
allowBlobPublicAccess: allowBlobPublicAccess
allowCrossTenantReplication: allowCrossTenantReplication
allowSharedKeyAccess: allowSharedKeyAccess
defaultToOAuthAuthentication: defaultToOAuthAuthentication
dnsEndpointType: dnsEndpointType
minimumTlsVersion: minimumTlsVersion
networkAcls: networkAcls
publicNetworkAccess: publicNetworkAccess
supportsHttpsTrafficOnly: supportsHttpsTrafficOnly
}

resource blobServices 'blobServices' = if (!empty(containers)) {
name: 'default'
properties: {
cors: {
corsRules: corsRules
}
deleteRetentionPolicy: deleteRetentionPolicy
}
resource container 'containers' = [for container in containers: {
name: container.name
properties: {
// todo: Warning use-safe-access: Use the safe access (.?) operator instead of checking object contents with the 'contains' function. [https://aka.ms/bicep/linter/use-safe-access]
publicAccess: contains(container, 'publicAccess') ? container.publicAccess : 'None'
}
}]
}

resource fileServices 'fileServices' = if (!empty(files)) {
name: 'default'
properties: {
cors: {
corsRules: corsRules
}
shareDeleteRetentionPolicy: shareDeleteRetentionPolicy
}
}

resource queueServices 'queueServices' = if (!empty(queues)) {
name: 'default'
properties: {

}
resource queue 'queues' = [for queue in queues: {
name: queue.name
properties: {
metadata: {}
}
}]
}

resource tableServices 'tableServices' = if (!empty(tables)) {
name: 'default'
properties: {}
// create tables pre-defined in aspire.bicep
resource table 'tables' = [for table in tables: {
name: table
properties: {}
}]
}
}

// Save Storage Account Connection String in Key Vault Secret
module keyVaultSecrets '../../core/security/keyvault-secret.bicep' = {
name: 'keyVaultSecrets'
params: {
name: 'storage-connection-string'
secretValue:'DefaultEndpointsProtocol=https;EndpointSuffix=${environment().suffixes.storage};AccountName=${storage.name};AccountKey=${storage.listKeys().keys[0].value};BlobEndpoint=${storage.properties.primaryEndpoints.blob};FileEndpoint=${storage.properties.primaryEndpoints.file};QueueEndpoint=${storage.properties.primaryEndpoints.queue};TableEndpoint=${storage.properties.primaryEndpoints.table}'
keyVaultName:keyVaultName
}
}

output id string = storage.id
output name string = storage.name
output primaryEndpoints object = storage.properties.primaryEndpoints
1 change: 1 addition & 0 deletions src/AzureOpenAIProxy.ApiApp/AzureOpenAIProxy.ApiApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" Version="$(AzureOpenAIVersion)" />
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
<PackageReference Include="Azure.Identity" Version="1.12.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="$(AspNetCoreVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class KeyVaultSettings
public string? VaultUri { get; set; }

/// <summary>
/// Gets or sets the secret name.
/// Gets or sets the secret names.
/// </summary>
public string? SecretName { get; set; }
public Dictionary<string, string> SecretNames { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AzureOpenAIProxy.ApiApp.Configurations;

/// <summary>
/// This represents the settings entity for storage account.
/// </summary>
public class StorageAccountSettings
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "StorageAccount";

/// <summary>
/// Gets or sets the <see cref="TableStorageSettings"/> instance.
/// </summary>
public TableStorageSettings TableStorage { get; set; } = new();
}
17 changes: 17 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Configurations/TableStorageSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AzureOpenAIProxy.ApiApp.Configurations;

/// <summary>
/// This represents the settings entity for Azure Table Stroage.
/// </summary>
public class TableStorageSettings
{
/// <summary>
/// Gets the name of the configuration settings.
/// </summary>
public const string Name = "TableStorage";

/// <summary>
/// Gets or sets the table name.
/// </summary>
public string? TableName { get; set; }
}
8 changes: 8 additions & 0 deletions src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEndpointUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ public static class AdminEndpointUrls
/// - POST method for new event creation
/// </remarks>
public const string AdminEvents = "/admin/events";

/// <summary>
/// Declares the admin resource details endpoint.
/// </summary>
/// <remarks>
/// - POST method for new resource creation
/// </remarks>
public const string AdminResources = "/admin/resources";
}
37 changes: 32 additions & 5 deletions src/AzureOpenAIProxy.ApiApp/Endpoints/AdminEventEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Azure;

using AzureOpenAIProxy.ApiApp.Models;
using AzureOpenAIProxy.ApiApp.Services;

Expand Down Expand Up @@ -107,15 +109,39 @@ public static RouteHandlerBuilder AddGetAdminEvent(this WebApplication app)
{
// Todo: Issue #19 https://github.com/aliencube/azure-openai-sdk-proxy/issues/19
// Need authorization by admin
var builder = app.MapGet(AdminEndpointUrls.AdminEventDetails, (
[FromRoute] string eventId) =>
var builder = app.MapGet(AdminEndpointUrls.AdminEventDetails, async (
[FromRoute] Guid eventId,
IAdminEventService service,
ILoggerFactory loggerFactory) =>
{
// Todo: Issue #208 https://github.com/aliencube/azure-openai-sdk-proxy/issues/208
return Results.Ok();
// Todo: Issue #208
var logger = loggerFactory.CreateLogger(nameof(AdminEventEndpoints));
logger.LogInformation($"Received request to fetch details for event with ID: {eventId}");
try
{
var details = await service.GetEvent(eventId);
return Results.Ok(details);
}
catch(RequestFailedException ex)
{
if(ex.Status == 404)
{
logger.LogError($"Failed to get event details of {eventId}");
return Results.NotFound();
}
logger.LogError(ex, $"Error occurred while fetching event details of {eventId} with status {ex.Status}");
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
catch(Exception ex)
{
logger.LogError(ex, $"Error occurred while fetching event details of {eventId}");
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
})
.Produces<AdminEventDetails>(statusCode: StatusCodes.Status200OK, contentType: "application/json")
.Produces(statusCode: StatusCodes.Status401Unauthorized)
.Produces(statusCode: StatusCodes.Status404NotFound)
.Produces<string>(statusCode: StatusCodes.Status500InternalServerError, contentType: "text/plain")
.WithTags("admin")
.WithName("GetAdminEvent")
Expand Down Expand Up @@ -148,6 +174,7 @@ public static RouteHandlerBuilder AddUpdateAdminEvent(this WebApplication app)
})
.Accepts<AdminEventDetails>(contentType: "application/json")
.Produces<AdminEventDetails>(statusCode: StatusCodes.Status200OK, contentType: "application/json")
.Produces(statusCode: StatusCodes.Status400BadRequest)
.Produces(statusCode: StatusCodes.Status401Unauthorized)
.Produces(statusCode: StatusCodes.Status404NotFound)
.Produces<string>(statusCode: StatusCodes.Status500InternalServerError, contentType: "text/plain")
Expand Down
Loading

0 comments on commit 95906e5

Please sign in to comment.