in cli/azd/extensions/microsoft.azd.ai.builder/internal/cmd/start.go [185:539]
func (a *startAction) Run(ctx context.Context, args []string) error {
// Build up list of questions
listOfQuestions, err := a.createQuestions(ctx)
if err != nil {
return fmt.Errorf("failed to generate prompt: %w", err)
}
decisionTree := qna.NewDecisionTree(listOfQuestions)
if err := decisionTree.Run(ctx); err != nil {
return fmt.Errorf("failed to run decision tree: %w", err)
}
spinner := ux.NewSpinner(&ux.SpinnerOptions{
Text: "Updating project configuration",
ClearOnStop: true,
})
fmt.Println()
if err := spinner.Start(ctx); err != nil {
return fmt.Errorf("failed to start spinner: %w", err)
}
resourcesToAdd := map[string]*azdext.ComposedResource{}
servicesToAdd := map[string]*azdext.ServiceConfig{}
// Add database resources
if a.scenarioData.DatabaseType != "" {
desiredName := strings.ReplaceAll(a.scenarioData.DatabaseType, "db.", "")
dbResource := &azdext.ComposedResource{
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.DatabaseType,
}
resourcesToAdd[dbResource.Name] = dbResource
}
// Add messaging resources
if a.scenarioData.MessagingType != "" {
desiredName := strings.ReplaceAll(a.scenarioData.MessagingType, "messaging.", "")
messagingResource := &azdext.ComposedResource{
Name: a.generateResourceName(desiredName),
Type: a.scenarioData.MessagingType,
}
resourcesToAdd[messagingResource.Name] = messagingResource
}
// Add vector store resources
if a.scenarioData.VectorStoreType != "" {
vectorStoreResource := &azdext.ComposedResource{
Name: a.generateResourceName("vector-store"),
Type: a.scenarioData.VectorStoreType,
}
resourcesToAdd[vectorStoreResource.Name] = vectorStoreResource
}
// Add storage resources
if a.scenarioData.UseCustomData && a.scenarioData.StorageAccountId != "" {
storageConfig := map[string]any{
"containers": []string{
"data",
"embeddings",
},
}
storageConfigJson, err := json.Marshal(storageConfig)
if err != nil {
return fmt.Errorf("failed to marshal storage config: %w", err)
}
storageResource := &azdext.ComposedResource{
Name: a.generateResourceName("storage"),
Type: "storage",
Config: storageConfigJson,
}
resourcesToAdd[storageResource.Name] = storageResource
}
models := []*ai.AiModelDeployment{}
type AiProjectResourceConfig struct {
Models []*ai.AiModelDeployment `json:"models,omitempty"`
}
// Add AI model resources
if len(a.scenarioData.ModelSelections) > 0 {
var aiProject *azdext.ComposedResource
var aiProjectConfig *AiProjectResourceConfig
for _, resource := range a.composedResources {
if resource.Type == "ai.project" {
aiProject = resource
if err := json.Unmarshal(resource.Config, &aiProjectConfig); err != nil {
return fmt.Errorf("failed to unmarshal AI project config: %w", err)
}
break
}
}
if aiProject == nil {
aiProject = &azdext.ComposedResource{
Name: a.generateResourceName("ai-project"),
Type: "ai.project",
}
aiProjectConfig = &AiProjectResourceConfig{}
}
modelMap := map[string]*ai.AiModelDeployment{}
for _, modelDeployment := range aiProjectConfig.Models {
modelMap[modelDeployment.Name] = modelDeployment
}
for _, modelName := range a.scenarioData.ModelSelections {
aiModel, exists := a.modelCatalog[modelName]
if exists {
modelDeployment, err := a.modelCatalogService.GetModelDeployment(ctx, aiModel, nil)
if err != nil {
return fmt.Errorf("failed to get model deployment: %w", err)
}
if _, has := modelMap[modelDeployment.Name]; !has {
modelMap[modelDeployment.Name] = modelDeployment
aiProjectConfig.Models = append(aiProjectConfig.Models, modelDeployment)
models = append(models, modelDeployment)
}
}
}
configJson, err := json.Marshal(aiProjectConfig)
if err != nil {
return fmt.Errorf("failed to marshal AI project config: %w", err)
}
aiProject.Config = configJson
resourcesToAdd[aiProject.Name] = aiProject
}
// Add host resources such as container apps.
for i, appKey := range a.scenarioData.InteractionTypes {
if i >= len(a.scenarioData.AppHostTypes) {
break
}
appType := a.scenarioData.AppHostTypes[i]
if appType == "" || appType == "choose-app" {
appType = "host.containerapp"
}
languageType := a.scenarioData.AppLanguages[i]
appConfig := map[string]any{
"port": 8080,
}
appConfigJson, err := json.Marshal(appConfig)
if err != nil {
return fmt.Errorf("failed to marshal app config: %w", err)
}
appResource := &azdext.ComposedResource{
Name: a.generateResourceName(appKey),
Type: appType,
Config: appConfigJson,
Uses: []string{},
}
serviceName := a.generateServiceName(appKey)
serviceConfig := &azdext.ServiceConfig{
Name: serviceName,
Language: languageType,
Host: strings.ReplaceAll(appType, "host.", ""),
RelativePath: filepath.Join("src", serviceName),
}
// Setting the key of the service to the scenario interaction type since this is used for the
// file copying.
servicesToAdd[appKey] = serviceConfig
resourcesToAdd[appResource.Name] = appResource
}
// Adds any new services to the azure.yaml.
for interactionName, service := range servicesToAdd {
_, err := a.azdClient.Project().AddService(ctx, &azdext.AddServiceRequest{
Service: service,
})
if err != nil {
return fmt.Errorf("failed to add service %s: %w", service.Name, err)
}
// Copy files from the embedded resources to the local service path.
destServicePath := filepath.Join(a.projectConfig.Path, service.RelativePath)
if err := os.MkdirAll(destServicePath, os.ModePerm); err != nil {
return fmt.Errorf("failed to create service path %s: %w", destServicePath, err)
}
if !util.IsDirEmpty(destServicePath) {
if err := spinner.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop spinner: %w", err)
}
overwriteResponse, err := a.azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
Options: &azdext.ConfirmOptions{
DefaultValue: to.Ptr(false),
Message: fmt.Sprintf(
"The directory %s is not empty. Do you want to overwrite it?",
output.WithHighLightFormat(service.RelativePath),
),
},
})
if err != nil {
return fmt.Errorf("failed to confirm overwrite: %w", err)
}
if !*overwriteResponse.Value {
continue
}
if err := spinner.Start(ctx); err != nil {
return fmt.Errorf("failed to start spinner: %w", err)
}
}
// Identify dependent resources.
uses := appUsesMap[interactionName]
resource := resourcesToAdd[service.Name]
resourceUseMap := map[string]struct{}{}
if len(uses) > 0 {
for _, dependentResource := range resourcesToAdd {
// Skip if the resource type is already added.
if _, has := resourceUseMap[dependentResource.Type]; has {
continue
}
if slices.Contains(uses, dependentResource.Type) && resource.Name != dependentResource.Name {
resource.Uses = append(resource.Uses, dependentResource.Name)
resourceUseMap[dependentResource.Type] = struct{}{}
}
}
for _, existingResource := range a.composedResources {
// Skip if the resource type is already added.
if _, has := resourceUseMap[existingResource.Type]; has {
continue
}
if slices.Contains(uses, existingResource.Type) && resource.Name != existingResource.Name {
resource.Uses = append(resource.Uses, existingResource.Name)
resourceUseMap[existingResource.Type] = struct{}{}
}
}
}
}
// Add any new resources to the azure.yaml.
for _, resource := range resourcesToAdd {
_, err := a.azdClient.Compose().AddResource(ctx, &azdext.AddResourceRequest{
Resource: resource,
})
if err != nil {
return fmt.Errorf("failed to add resource %s: %w", resource.Name, err)
}
}
if err := spinner.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop spinner: %w", err)
}
fmt.Println(output.WithSuccessFormat("SUCCESS! The following have been staged for provisioning and deployment:"))
if len(servicesToAdd) > 0 {
fmt.Println()
fmt.Println(output.WithHintFormat("Services"))
for _, service := range servicesToAdd {
fmt.Printf(" - %s %s\n",
service.Name,
output.WithGrayFormat(
"(Host: %s, Language: %s)",
service.Host,
service.Language,
),
)
}
}
if len(resourcesToAdd) > 0 {
fmt.Println()
fmt.Println(output.WithHintFormat("Resources"))
for _, resource := range resourcesToAdd {
fmt.Printf(" - %s %s\n", resource.Name, output.WithGrayFormat("(%s)", resource.Type))
}
}
if len(models) > 0 {
fmt.Println()
fmt.Println(output.WithHintFormat("AI Models"))
for _, modelDeployment := range models {
fmt.Printf(" - %s %s\n",
modelDeployment.Name,
output.WithGrayFormat(
"(Format: %s, Version: %s, SKU: %s)",
modelDeployment.Format,
modelDeployment.Version,
modelDeployment.Sku.Name,
),
)
}
}
fmt.Println()
confirmResponse, err := a.azdClient.Prompt().Confirm(ctx, &azdext.ConfirmRequest{
Options: &azdext.ConfirmOptions{
Message: "Do you want to provision resources to your project now?",
DefaultValue: to.Ptr(true),
HelpMessage: "Provisioning resources will create the necessary Azure infrastructure for your application.",
},
})
if err != nil {
return fmt.Errorf("failed to confirm provisioning: %w", err)
}
if !*confirmResponse.Value {
fmt.Println()
fmt.Printf("To provision resources later, run %s\n", output.WithHighLightFormat("azd provision"))
return nil
}
workflow := &azdext.Workflow{
Name: "provision",
Steps: []*azdext.WorkflowStep{
{
Command: &azdext.WorkflowCommand{
Args: []string{"provision"},
},
},
},
}
_, err = a.azdClient.Workflow().Run(ctx, &azdext.RunWorkflowRequest{
Workflow: workflow,
})
if err != nil {
return fmt.Errorf("failed to run provision workflow: %w", err)
}
fmt.Println()
fmt.Println(output.WithSuccessFormat("SUCCESS! Your Azure resources have been provisioned."))
fmt.Printf(
"You can add additional resources to your project by running %s\n",
output.WithHighLightFormat("azd compose add"),
)
return nil
}