Skip to content

compose - AI Foundry 1RP changes #5157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions cli/azd/internal/scaffold/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"regexp"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/tidwall/gjson"
)

// BicepName returns a name suitable for use as a bicep variable name.
Expand Down Expand Up @@ -267,27 +267,22 @@ func hostFromEndpoint(endpoint string) (string, error) {
return urlEndpoint.Hostname(), nil
}

func aiProjectConnectionString(resourceId string, projectUrl string) (string, error) {
hostName, err := hostFromEndpoint(projectUrl)
if err != nil {
return "", fmt.Errorf("failed to parse project URL: %w", err)
func aiProjectEndpoint(endpoints string) (string, error) {
result := gjson.Parse(endpoints)
if !result.Exists() {
return "", fmt.Errorf("invalid endpoints JSON: %s", endpoints)
}

resId, err := arm.ParseResourceID(resourceId)
if err != nil {
return "", nil
endpoint := result.Get("AI Foundry API")
if !endpoint.Exists() {
return "", fmt.Errorf("endpoint 'AI Foundry API' not found in endpoints")
}

return fmt.Sprintf("%s;%s;%s;%s", hostName, resId.SubscriptionID, resId.ResourceGroupName, resId.Name), nil
return endpoint.String(), nil
}

func emitAiProjectConnectionString(resourceIdVar string, projectUrlVar string) (string, error) {
return fmt.Sprintf(
"${split(%s, '/')[2]};${split(%s, '/')[2]};${split(%s, '/')[4]};${split(%s, '/')[8]}",
projectUrlVar,
resourceIdVar,
resourceIdVar,
resourceIdVar), nil
func emitAiProjectEndpoint(projectEndpointsVar string) (string, error) {
return fmt.Sprintf("%s['AI Foundry API']", projectEndpointsVar), nil
}

func emitHostFromEndpoint(endpointVar string) (string, error) {
Expand Down
20 changes: 10 additions & 10 deletions cli/azd/internal/scaffold/resource_expr_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ type FuncMap map[string]any
// The functions are evaluated at runtime against live Azure resources.
func BaseEvalFuncMap() FuncMap {
return FuncMap{
"lower": strings.ToLower,
"upper": strings.ToUpper,
"replace": strings.ReplaceAll,
"host": hostFromEndpoint,
"aiProjectConnectionString": aiProjectConnectionString,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"replace": strings.ReplaceAll,
"host": hostFromEndpoint,
"aiProjectEndpoint": aiProjectEndpoint,
}
}

Expand All @@ -57,11 +57,11 @@ func BaseEvalFuncMap() FuncMap {
// The functions are not evaluated at runtime, but rather emitted as part of the Bicep template.
func BaseEmitBicepFuncMap() FuncMap {
return FuncMap{
"lower": bicepFuncCall("toLower"),
"upper": bicepFuncCall("toUpper"),
"replace": bicepFuncCallThree("replace"),
"host": emitHostFromEndpoint,
"aiProjectConnectionString": emitAiProjectConnectionString,
"lower": bicepFuncCall("toLower"),
"upper": bicepFuncCall("toUpper"),
"replace": bicepFuncCallThree("replace"),
"host": emitHostFromEndpoint,
"aiProjectEndpoint": emitAiProjectEndpoint,
}
}

Expand Down
55 changes: 30 additions & 25 deletions cli/azd/internal/scaffold/resource_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,42 @@ var Resources = []ResourceMeta{
},
{
ResourceType: "Microsoft.CognitiveServices/accounts",
ApiVersion: "2023-05-01",
ApiVersion: "2025-04-01-preview",
},
{
ResourceType: "Microsoft.CognitiveServices/accounts/deployments",
ApiVersion: "2023-05-01",
ApiVersion: "2025-04-01-preview",
ParentForEval: "Microsoft.CognitiveServices/accounts",
StandardVarPrefix: "AZURE_OPENAI",
Variables: map[string]string{
"endpoint": "${.properties.endpoint}",
},
},
{
ResourceType: "Microsoft.CognitiveServices/accounts/projects",
ResourceKind: "AIServices",
ApiVersion: "2025-04-01-preview",
StandardVarPrefix: "AZURE_AI_PROJECT",
Variables: map[string]string{
"endpoint": "${aiProjectEndpoint .properties.endpoints}",
},
RoleAssignments: RoleAssignments{
Write: []RoleAssignment{
{
Name: "AzureAIDeveloper",
RoleDefinitionName: "Azure AI Developer",
RoleDefinitionId: "64702f94-c441-49e6-a78b-ef80e0188fee",
Scope: RoleAssignmentScopeGroup,
},
{
Name: "CognitiveServicesUser",
RoleDefinitionName: "Cognitive Services User",
RoleDefinitionId: "a97b65f3-24c7-4388-baec-2e87135dc908",
Scope: RoleAssignmentScopeGroup,
},
},
},
},
{
ResourceType: "Microsoft.ContainerRegistry/registries",
ApiVersion: "2023-06-01-preview",
Expand Down Expand Up @@ -235,31 +260,11 @@ var Resources = []ResourceMeta{
},
},
{
ResourceType: "Microsoft.MachineLearningServices/workspaces",
ResourceKind: "Project",
ApiVersion: "2024-10-01",
StandardVarPrefix: "AZURE_AI_PROJECT",
Variables: map[string]string{
"connectionString": "${aiProjectConnectionString .id .properties.discoveryUrl}",
},
RoleAssignments: RoleAssignments{
Write: []RoleAssignment{
{
Name: "AIDeveloper",
RoleDefinitionName: "Azure AI Developer",
RoleDefinitionId: "64702f94-c441-49e6-a78b-ef80e0188fee",
Scope: RoleAssignmentScopeGroup,
},
},
},
},
{
ResourceType: "Microsoft.Search/searchServices",
// TODO: Switch to 2025-02-01-preview once available, which has a new 'endpoint' property
ApiVersion: "2024-06-01-preview",
ResourceType: "Microsoft.Search/searchServices",
ApiVersion: "2025-02-01-preview",
StandardVarPrefix: "AZURE_AI_SEARCH",
Variables: map[string]string{
"endpoint": "https://${.name}.search.windows.net",
"endpoint": "${.properties.endpoint}",
},
RoleAssignments: RoleAssignments{
Write: []RoleAssignment{
Expand Down
1 change: 1 addition & 0 deletions cli/azd/pkg/azapi/azure_resource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
AzureResourceTypeDevCenterProject AzureResourceType = "Microsoft.DevCenter/projects"
AzureResourceTypeMachineLearningWorkspace AzureResourceType = "Microsoft.MachineLearningServices/workspaces"
AzureResourceTypeMachineLearningConnection AzureResourceType = "Microsoft.MachineLearningServices/workspaces/connections"
AzureResourceTypeRoleAssignment AzureResourceType = "Microsoft.Authorization/roleAssignments"

//nolint:lll
AzureResourceTypeMachineLearningEndpoint AzureResourceType = "Microsoft.MachineLearningServices/workspaces/onlineEndpoints"
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/pkg/project/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (r ResourceType) AzureResourceType() string {
case ResourceTypeKeyVault:
return "Microsoft.KeyVault/vaults"
case ResourceTypeAiProject:
return "Microsoft.MachineLearningServices/workspaces"
return "Microsoft.CognitiveServices/accounts/projects"
case ResourceTypeAiSearch:
return "Microsoft.Search/searchServices"
}
Expand Down
63 changes: 44 additions & 19 deletions cli/azd/resources/scaffold/base/modules/ai-search-conn.bicep
Original file line number Diff line number Diff line change
@@ -1,30 +1,55 @@
@description('The name of the AI Hub')
param aiHubName string
@description('The name of the AI Services account')
param aiServicesName string

@description('The name of the AI Services project')
param aiServicesProjectName string

@description('The name of the AI Search')
param aiSearchName string

resource search 'Microsoft.Search/searchServices@2024-06-01-preview' existing = {
resource search 'Microsoft.Search/searchServices@2025-02-01-preview' existing = {
name: aiSearchName
}

resource hub 'Microsoft.MachineLearningServices/workspaces@2024-10-01' existing = {
name: aiHubName

resource AzureAISearch 'connections@2024-10-01' = {
name: 'AzureAISearch-connection'
properties: {
category: 'CognitiveSearch'
target: 'https://${search.name}.search.windows.net'
authType: 'ApiKey'
isSharedToAll: true
credentials: {
key: search.listAdminKeys().primaryKey
}
metadata: {
ApiType: 'Azure'
ResourceId: search.id
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = {
name: aiServicesName

resource project 'projects' existing = {
name: aiServicesProjectName

resource AzureAISearch 'connections' = {
name: 'AzureAISearch-connection'
properties: {
category: 'CognitiveSearch'
target: search.properties.endpoint
authType: 'AAD'
isSharedToAll: true
metadata: {
ApiType: 'Azure'
ResourceId: search.id
location: search.location
}
}
}
}
}

resource projectSearchIndexDataContributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: search
name: guid(subscription().id, resourceGroup().id, aiServices::project.id, '8ebe5a00-799e-43f5-93ac-243d3dce84a7')
properties: {
principalId: aiServices::project.identity.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '8ebe5a00-799e-43f5-93ac-243d3dce84a7')
}
}

resource projectSearchServiceContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: search
name: guid(subscription().id, resourceGroup().id, aiServices::project.id, '7ca78c08-252a-4471-8644-bb5ff32d4ba0')
properties: {
principalId: aiServices::project.identity.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '7ca78c08-252a-4471-8644-bb5ff32d4ba0')
}
}
Loading
Loading