in cli/azd/pkg/project/scaffold_gen.go [137:355]
func infraSpec(projectConfig *ProjectConfig) (*scaffold.InfraSpec, error) {
infraSpec := scaffold.InfraSpec{}
existingMap := map[string]*scaffold.ExistingResource{}
// backends -> frontends
backendMapping := map[string]string{}
// Create a "virtual" copy since we're adding any implicitly dependent resources
// that are unrepresented by the current user-provided schema
resources := maps.Clone(projectConfig.Resources)
keys := slices.Sorted(maps.Keys(resources))
// First pass
for _, k := range keys {
res := resources[k]
// Add any implicit dependencies
dependencies := DependentResourcesOf(res)
for _, dep := range dependencies {
if _, exists := resources[dep.Name]; !exists {
resources[dep.Name] = dep
}
}
if res.Existing { // handle existing flow
resourceMeta, ok := scaffold.ResourceMetaFromType(res.Type.AzureResourceType())
if !ok {
return nil, fmt.Errorf("resource type '%s' is not currently supported for existing", string(res.Type))
}
existing := scaffold.ExistingResource{
Name: "existing" + scaffold.BicepNameInfix(res.Name),
ApiVersion: resourceMeta.ApiVersion,
ResourceIdEnvVar: infra.ResourceIdName(res.Name),
ResourceType: resourceMeta.ResourceType,
RoleAssignments: resourceMeta.RoleAssignments.Write,
}
if resourceMeta.ParentForEval != "" {
existing.ResourceType = resourceMeta.ParentForEval
}
if res.Type == ResourceTypeKeyVault {
// For Key Vault, we grant read access to secrets by default
existing.RoleAssignments = resourceMeta.RoleAssignments.Read
}
infraSpec.Existing = append(infraSpec.Existing, existing)
existingMap[res.Name] = &existing
continue
}
}
for _, k := range keys {
res := resources[k]
if res.Existing {
continue
}
switch res.Type {
case ResourceTypeDbRedis:
infraSpec.DbRedis = &scaffold.DatabaseRedis{}
case ResourceTypeDbMongo:
infraSpec.DbCosmosMongo = &scaffold.DatabaseCosmosMongo{
DatabaseName: res.Name,
}
case ResourceTypeDbCosmos:
props := res.Props.(CosmosDBProps)
containers := make([]scaffold.CosmosSqlDatabaseContainer, 0)
for _, c := range props.Containers {
containers = append(containers, scaffold.CosmosSqlDatabaseContainer{
ContainerName: c.Name,
PartitionKeyPaths: c.PartitionKeys,
})
}
infraSpec.DbCosmos = &scaffold.DatabaseCosmos{
DatabaseName: res.Name,
Containers: containers,
}
case ResourceTypeDbPostgres:
infraSpec.DbPostgres = &scaffold.DatabasePostgres{
DatabaseName: res.Name,
}
case ResourceTypeDbMySql:
infraSpec.DbMySql = &scaffold.DatabaseMysql{
DatabaseName: res.Name,
}
case ResourceTypeHostAppService:
svcConfig, ok := projectConfig.Services[res.Name]
if !ok {
return nil, fmt.Errorf("service %s not found in project config", res.Name)
}
svcSpec := scaffold.ServiceSpec{
Name: res.Name,
Port: -1,
Env: map[string]string{},
Host: scaffold.AppServiceKind,
}
err := mapAppService(res, &svcSpec, &infraSpec, svcConfig)
if err != nil {
return nil, err
}
err = mapHostUses(res, &svcSpec, backendMapping, existingMap, projectConfig)
if err != nil {
return nil, err
}
infraSpec.Services = append(infraSpec.Services, svcSpec)
case ResourceTypeHostContainerApp:
svcSpec := scaffold.ServiceSpec{
Name: res.Name,
Port: -1,
Env: map[string]string{},
Host: scaffold.ContainerAppKind,
}
err := mapContainerApp(res, &svcSpec, &infraSpec)
if err != nil {
return nil, err
}
err = mapHostUses(res, &svcSpec, backendMapping, existingMap, projectConfig)
if err != nil {
return nil, err
}
infraSpec.Services = append(infraSpec.Services, svcSpec)
case ResourceTypeOpenAiModel:
props := res.Props.(AIModelProps)
if len(props.Model.Name) == 0 {
return nil, fmt.Errorf("resources.%s.model is required", res.Name)
}
if len(props.Model.Version) == 0 {
return nil, fmt.Errorf("resources.%s.version is required", res.Name)
}
infraSpec.AIModels = append(infraSpec.AIModels, scaffold.AIModel{
Name: res.Name,
Model: scaffold.AIModelModel{
Name: props.Model.Name,
Version: props.Model.Version,
},
})
case ResourceTypeMessagingEventHubs:
if infraSpec.EventHubs != nil {
return nil, fmt.Errorf("only one event hubs resource is currently allowed")
}
props := res.Props.(EventHubsProps)
infraSpec.EventHubs = &scaffold.EventHubs{
Hubs: props.Hubs,
}
case ResourceTypeMessagingServiceBus:
if infraSpec.ServiceBus != nil {
return nil, fmt.Errorf("only one service bus resource is currently allowed")
}
props := res.Props.(ServiceBusProps)
infraSpec.ServiceBus = &scaffold.ServiceBus{
Queues: props.Queues,
Topics: props.Topics,
}
case ResourceTypeStorage:
if infraSpec.StorageAccount != nil {
return nil, fmt.Errorf("only one storage account resource is currently allowed")
}
props := res.Props.(StorageProps)
infraSpec.StorageAccount = &scaffold.StorageAccount{
Containers: props.Containers,
}
case ResourceTypeAiProject:
// It's okay to forcefully panic here. The only way we would land here is that the marshal/unmarshal
// in resources.go was not done right.
props := res.Props.(AiFoundryModelProps)
foundryName := res.Name
var foundryModels []scaffold.AiFoundryModel
foundrySpec := scaffold.AiFoundrySpec{
Name: foundryName,
}
for _, model := range props.Models {
foundryModels = append(foundryModels, scaffold.AiFoundryModel{
AIModelModel: scaffold.AIModelModel{
Name: model.Name,
Version: model.Version,
},
Format: model.Format,
Sku: scaffold.AiFoundryModelSku{
Name: model.Sku.Name,
UsageName: model.Sku.UsageName,
Capacity: model.Sku.Capacity,
},
})
}
foundrySpec.Models = foundryModels
infraSpec.AiFoundryProject = &foundrySpec
case ResourceTypeKeyVault:
infraSpec.KeyVault = &scaffold.KeyVault{}
case ResourceTypeAiSearch:
infraSpec.AISearch = &scaffold.AISearch{}
}
}
// create reverse frontends -> backends mapping
for i := range infraSpec.Services {
svc := &infraSpec.Services[i]
if front, ok := backendMapping[svc.Name]; ok {
if svc.Backend == nil {
svc.Backend = &scaffold.Backend{}
}
svc.Backend.Frontends = append(svc.Backend.Frontends, scaffold.ServiceReference{Name: front})
}
}
slices.SortFunc(infraSpec.Services, func(a, b scaffold.ServiceSpec) int {
return strings.Compare(a.Name, b.Name)
})
return &infraSpec, nil
}