Skip to content

Commit 9d71912

Browse files
authored
easy-init: generate resources in azure.yaml (#4509)
Generate resources in `azure.yaml` and delay infrastructure generation if `alpha.compose` is enabled Contributes to #4397, Azure/azure-dev-pr#1682
1 parent dd46ca9 commit 9d71912

File tree

6 files changed

+538
-28
lines changed

6 files changed

+538
-28
lines changed

cli/azd/internal/repository/app_init.go

+138-12
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package repository
33
import (
44
"context"
55
"fmt"
6+
"maps"
67
"os"
78
"path/filepath"
9+
"slices"
10+
"strings"
811
"time"
912

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

45+
var featureCompose = alpha.MustFeatureKey("compose")
46+
4247
// InitFromApp initializes the infra directory and project file from the current existing app.
4348
func (i *Initializer) InitFromApp(
4449
ctx context.Context,
@@ -244,30 +249,31 @@ func (i *Initializer) InitFromApp(
244249

245250
// Create the infra spec
246251
var infraSpec *scaffold.InfraSpec
247-
if !i.features.IsEnabled(alpha.Compose) { // backwards compatibility
252+
composeEnabled := i.features.IsEnabled(featureCompose)
253+
if !composeEnabled { // backwards compatibility
248254
spec, err := i.infraSpecFromDetect(ctx, detect)
249255
if err != nil {
250256
return err
251257
}
252258
infraSpec = &spec
253-
}
254259

255-
// Prompt for environment before proceeding with generation
256-
_, err = initializeEnv()
257-
if err != nil {
258-
return err
260+
// Prompt for environment before proceeding with generation
261+
_, err = initializeEnv()
262+
if err != nil {
263+
return err
264+
}
259265
}
260266

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

263-
i.console.Message(ctx, "\n"+output.WithBold("Generating files to run your app on Azure:")+"\n")
264269
title = "Generating " + output.WithHighLightFormat("./"+azdcontext.ProjectFileName)
265270
i.console.ShowSpinner(ctx, title, input.Step)
266-
err = i.genProjectFile(ctx, azdCtx, detect)
271+
err = i.genProjectFile(ctx, azdCtx, detect, composeEnabled)
267272
if err != nil {
268273
i.console.StopSpinner(ctx, title, input.GetStepResultFormat(err))
269274
return err
270275
}
276+
i.console.Message(ctx, "\n"+output.WithBold("Generating files to run your app on Azure:")+"\n")
271277
i.console.StopSpinner(ctx, title, input.StepDone)
272278

273279
if infraSpec != nil {
@@ -279,6 +285,20 @@ func (i *Initializer) InitFromApp(
279285
return err
280286
}
281287
i.console.StopSpinner(ctx, title, input.StepDone)
288+
} else {
289+
t, err := scaffold.Load()
290+
if err != nil {
291+
return fmt.Errorf("loading scaffold templates: %w", err)
292+
}
293+
294+
err = scaffold.Execute(t, "next-steps-alpha.md", nil, filepath.Join(azdCtx.ProjectDirectory(), "next-steps.md"))
295+
if err != nil {
296+
return err
297+
}
298+
299+
i.console.MessageUxItem(ctx, &ux.DoneMessage{
300+
Message: "Generating " + output.WithHighLightFormat("./next-steps.md"),
301+
})
282302
}
283303

284304
return nil
@@ -341,8 +361,9 @@ func (i *Initializer) genFromInfra(
341361
func (i *Initializer) genProjectFile(
342362
ctx context.Context,
343363
azdCtx *azdcontext.AzdContext,
344-
detect detectConfirm) error {
345-
config, err := prjConfigFromDetect(azdCtx.ProjectDirectory(), detect)
364+
detect detectConfirm,
365+
addResources bool) error {
366+
config, err := i.prjConfigFromDetect(ctx, azdCtx.ProjectDirectory(), detect, addResources)
346367
if err != nil {
347368
return fmt.Errorf("converting config: %w", err)
348369
}
@@ -359,16 +380,20 @@ func (i *Initializer) genProjectFile(
359380

360381
const InitGenTemplateId = "azd-init"
361382

362-
func prjConfigFromDetect(
383+
func (i *Initializer) prjConfigFromDetect(
384+
ctx context.Context,
363385
root string,
364-
detect detectConfirm) (project.ProjectConfig, error) {
386+
detect detectConfirm,
387+
addResources bool) (project.ProjectConfig, error) {
365388
config := project.ProjectConfig{
366389
Name: azdcontext.ProjectName(root),
367390
Metadata: &project.ProjectMetadata{
368391
Template: fmt.Sprintf("%s@%s", InitGenTemplateId, internal.VersionInfo().Version),
369392
},
370393
Services: map[string]*project.ServiceConfig{},
371394
}
395+
396+
svcMapping := map[string]string{}
372397
for _, prj := range detect.Services {
373398
rel, err := filepath.Rel(root, prj.Path)
374399
if err != nil {
@@ -429,7 +454,108 @@ func prjConfigFromDetect(
429454
name = config.Name
430455
}
431456
name = names.LabelName(name)
457+
svc.Name = name
432458
config.Services[name] = &svc
459+
460+
svcMapping[prj.Path] = name
461+
}
462+
463+
if addResources {
464+
config.Resources = map[string]*project.ResourceConfig{}
465+
dbNames := map[appdetect.DatabaseDep]string{}
466+
467+
databases := slices.SortedFunc(maps.Keys(detect.Databases),
468+
func(a appdetect.DatabaseDep, b appdetect.DatabaseDep) int {
469+
return strings.Compare(string(a), string(b))
470+
})
471+
472+
for _, database := range databases {
473+
if database == appdetect.DbRedis {
474+
redis := project.ResourceConfig{
475+
Type: project.ResourceTypeDbRedis,
476+
Name: "redis",
477+
}
478+
config.Resources[redis.Name] = &redis
479+
dbNames[database] = redis.Name
480+
continue
481+
}
482+
483+
var dbType project.ResourceType
484+
switch database {
485+
case appdetect.DbMongo:
486+
dbType = project.ResourceTypeDbMongo
487+
case appdetect.DbPostgres:
488+
dbType = project.ResourceTypeDbPostgres
489+
}
490+
491+
db := project.ResourceConfig{
492+
Type: dbType,
493+
}
494+
495+
for {
496+
dbName, err := promptDbName(i.console, ctx, database)
497+
if err != nil {
498+
return config, err
499+
}
500+
501+
if dbName == "" {
502+
i.console.Message(ctx, "Database name is required.")
503+
continue
504+
}
505+
506+
db.Name = dbName
507+
break
508+
}
509+
510+
config.Resources[db.Name] = &db
511+
dbNames[database] = db.Name
512+
}
513+
514+
backends := []*project.ResourceConfig{}
515+
frontends := []*project.ResourceConfig{}
516+
517+
for _, svc := range detect.Services {
518+
name := svcMapping[svc.Path]
519+
resSpec := project.ResourceConfig{
520+
Type: project.ResourceTypeHostContainerApp,
521+
}
522+
523+
props := project.ContainerAppProps{
524+
Port: -1,
525+
}
526+
527+
port, err := promptPort(i.console, ctx, name, svc)
528+
if err != nil {
529+
return config, err
530+
}
531+
props.Port = port
532+
533+
for _, db := range svc.DatabaseDeps {
534+
// filter out databases that were removed
535+
if _, ok := detect.Databases[db]; !ok {
536+
continue
537+
}
538+
539+
resSpec.Uses = append(resSpec.Uses, dbNames[db])
540+
}
541+
542+
resSpec.Name = name
543+
resSpec.Props = props
544+
config.Resources[name] = &resSpec
545+
546+
frontend := svc.HasWebUIFramework()
547+
if frontend {
548+
frontends = append(frontends, &resSpec)
549+
} else {
550+
backends = append(backends, &resSpec)
551+
}
552+
}
553+
554+
for _, frontend := range frontends {
555+
for _, backend := range backends {
556+
frontend.Uses = append(frontend.Uses, backend.Name)
557+
}
558+
}
433559
}
434560

435561
return config, nil

0 commit comments

Comments
 (0)