Skip to content

Commit

Permalink
easy-init: generate resources in azure.yaml (Azure#4509)
Browse files Browse the repository at this point in the history
Generate resources in `azure.yaml` and delay infrastructure generation if `alpha.compose` is enabled

Contributes to Azure#4397,  Azure/azure-dev-pr#1682
  • Loading branch information
weikanglim authored Nov 7, 2024
1 parent dd46ca9 commit 9d71912
Show file tree
Hide file tree
Showing 6 changed files with 538 additions and 28 deletions.
150 changes: 138 additions & 12 deletions cli/azd/internal/repository/app_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package repository
import (
"context"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strings"
"time"

"github.com/azure/azure-dev/cli/azd/internal"
Expand Down Expand Up @@ -39,6 +42,8 @@ var dbMap = map[appdetect.DatabaseDep]struct{}{
appdetect.DbRedis: {},
}

var featureCompose = alpha.MustFeatureKey("compose")

// InitFromApp initializes the infra directory and project file from the current existing app.
func (i *Initializer) InitFromApp(
ctx context.Context,
Expand Down Expand Up @@ -244,30 +249,31 @@ func (i *Initializer) InitFromApp(

// Create the infra spec
var infraSpec *scaffold.InfraSpec
if !i.features.IsEnabled(alpha.Compose) { // backwards compatibility
composeEnabled := i.features.IsEnabled(featureCompose)
if !composeEnabled { // backwards compatibility
spec, err := i.infraSpecFromDetect(ctx, detect)
if err != nil {
return err
}
infraSpec = &spec
}

// Prompt for environment before proceeding with generation
_, err = initializeEnv()
if err != nil {
return err
// Prompt for environment before proceeding with generation
_, err = initializeEnv()
if err != nil {
return err
}
}

tracing.SetUsageAttributes(fields.AppInitLastStep.String("generate"))

i.console.Message(ctx, "\n"+output.WithBold("Generating files to run your app on Azure:")+"\n")
title = "Generating " + output.WithHighLightFormat("./"+azdcontext.ProjectFileName)
i.console.ShowSpinner(ctx, title, input.Step)
err = i.genProjectFile(ctx, azdCtx, detect)
err = i.genProjectFile(ctx, azdCtx, detect, composeEnabled)
if err != nil {
i.console.StopSpinner(ctx, title, input.GetStepResultFormat(err))
return err
}
i.console.Message(ctx, "\n"+output.WithBold("Generating files to run your app on Azure:")+"\n")
i.console.StopSpinner(ctx, title, input.StepDone)

if infraSpec != nil {
Expand All @@ -279,6 +285,20 @@ func (i *Initializer) InitFromApp(
return err
}
i.console.StopSpinner(ctx, title, input.StepDone)
} else {
t, err := scaffold.Load()
if err != nil {
return fmt.Errorf("loading scaffold templates: %w", err)
}

err = scaffold.Execute(t, "next-steps-alpha.md", nil, filepath.Join(azdCtx.ProjectDirectory(), "next-steps.md"))
if err != nil {
return err
}

i.console.MessageUxItem(ctx, &ux.DoneMessage{
Message: "Generating " + output.WithHighLightFormat("./next-steps.md"),
})
}

return nil
Expand Down Expand Up @@ -341,8 +361,9 @@ func (i *Initializer) genFromInfra(
func (i *Initializer) genProjectFile(
ctx context.Context,
azdCtx *azdcontext.AzdContext,
detect detectConfirm) error {
config, err := prjConfigFromDetect(azdCtx.ProjectDirectory(), detect)
detect detectConfirm,
addResources bool) error {
config, err := i.prjConfigFromDetect(ctx, azdCtx.ProjectDirectory(), detect, addResources)
if err != nil {
return fmt.Errorf("converting config: %w", err)
}
Expand All @@ -359,16 +380,20 @@ func (i *Initializer) genProjectFile(

const InitGenTemplateId = "azd-init"

func prjConfigFromDetect(
func (i *Initializer) prjConfigFromDetect(
ctx context.Context,
root string,
detect detectConfirm) (project.ProjectConfig, error) {
detect detectConfirm,
addResources bool) (project.ProjectConfig, error) {
config := project.ProjectConfig{
Name: azdcontext.ProjectName(root),
Metadata: &project.ProjectMetadata{
Template: fmt.Sprintf("%s@%s", InitGenTemplateId, internal.VersionInfo().Version),
},
Services: map[string]*project.ServiceConfig{},
}

svcMapping := map[string]string{}
for _, prj := range detect.Services {
rel, err := filepath.Rel(root, prj.Path)
if err != nil {
Expand Down Expand Up @@ -429,7 +454,108 @@ func prjConfigFromDetect(
name = config.Name
}
name = names.LabelName(name)
svc.Name = name
config.Services[name] = &svc

svcMapping[prj.Path] = name
}

if addResources {
config.Resources = map[string]*project.ResourceConfig{}
dbNames := map[appdetect.DatabaseDep]string{}

databases := slices.SortedFunc(maps.Keys(detect.Databases),
func(a appdetect.DatabaseDep, b appdetect.DatabaseDep) int {
return strings.Compare(string(a), string(b))
})

for _, database := range databases {
if database == appdetect.DbRedis {
redis := project.ResourceConfig{
Type: project.ResourceTypeDbRedis,
Name: "redis",
}
config.Resources[redis.Name] = &redis
dbNames[database] = redis.Name
continue
}

var dbType project.ResourceType
switch database {
case appdetect.DbMongo:
dbType = project.ResourceTypeDbMongo
case appdetect.DbPostgres:
dbType = project.ResourceTypeDbPostgres
}

db := project.ResourceConfig{
Type: dbType,
}

for {
dbName, err := promptDbName(i.console, ctx, database)
if err != nil {
return config, err
}

if dbName == "" {
i.console.Message(ctx, "Database name is required.")
continue
}

db.Name = dbName
break
}

config.Resources[db.Name] = &db
dbNames[database] = db.Name
}

backends := []*project.ResourceConfig{}
frontends := []*project.ResourceConfig{}

for _, svc := range detect.Services {
name := svcMapping[svc.Path]
resSpec := project.ResourceConfig{
Type: project.ResourceTypeHostContainerApp,
}

props := project.ContainerAppProps{
Port: -1,
}

port, err := promptPort(i.console, ctx, name, svc)
if err != nil {
return config, err
}
props.Port = port

for _, db := range svc.DatabaseDeps {
// filter out databases that were removed
if _, ok := detect.Databases[db]; !ok {
continue
}

resSpec.Uses = append(resSpec.Uses, dbNames[db])
}

resSpec.Name = name
resSpec.Props = props
config.Resources[name] = &resSpec

frontend := svc.HasWebUIFramework()
if frontend {
frontends = append(frontends, &resSpec)
} else {
backends = append(backends, &resSpec)
}
}

for _, frontend := range frontends {
for _, backend := range backends {
frontend.Uses = append(frontend.Uses, backend.Name)
}
}
}

return config, nil
Expand Down
Loading

0 comments on commit 9d71912

Please sign in to comment.