func infraSpec()

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
}