From 31744c9786a68538b41387d5928502454b6dade2 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 25 Nov 2024 17:21:52 +0800 Subject: [PATCH 01/19] add dependsOn in azure.yaml, then reflect on the infraSpec, to exec bicep template --- cli/azd/internal/appdetect/appdetect.go | 13 ++++++ cli/azd/internal/appdetect/spring_boot.go | 43 ++++++++++++++++++- cli/azd/internal/repository/app_init.go | 28 ++++++++++++ cli/azd/internal/repository/infra_confirm.go | 8 ++++ cli/azd/internal/scaffold/spec.go | 10 ++++- cli/azd/pkg/project/resources.go | 10 ++++- cli/azd/pkg/project/scaffold_gen.go | 8 +++- .../scaffold/templates/resources.bicept | 15 +++++++ schemas/alpha/azure.yaml.json | 21 +++++++++ 9 files changed, 149 insertions(+), 7 deletions(-) diff --git a/cli/azd/internal/appdetect/appdetect.go b/cli/azd/internal/appdetect/appdetect.go index 77a362f140b..d920ec8ae5b 100644 --- a/cli/azd/internal/appdetect/appdetect.go +++ b/cli/azd/internal/appdetect/appdetect.go @@ -59,6 +59,11 @@ const ( PyFlask Dependency = "flask" PyDjango Dependency = "django" PyFastApi Dependency = "fastapi" + + EurekaServer Dependency = "eureka-server" + EurekaClient Dependency = "eureka-client" + ConfigServer Dependency = "config-server" + ConfigClient Dependency = "config-client" ) var WebUIFrameworks = map[Dependency]struct{}{ @@ -89,6 +94,14 @@ func (f Dependency) Display() string { return "Vite" case JsNext: return "Next.js" + case EurekaServer: + return "EurekaServer" + case EurekaClient: + return "EurekaClient" + case ConfigServer: + return "ConfigServer" + case ConfigClient: + return "ConfigClient" } return "" diff --git a/cli/azd/internal/appdetect/spring_boot.go b/cli/azd/internal/appdetect/spring_boot.go index b0d33a5164f..ff00e9a41d8 100644 --- a/cli/azd/internal/appdetect/spring_boot.go +++ b/cli/azd/internal/appdetect/spring_boot.go @@ -98,6 +98,8 @@ func detectAzureDependenciesByAnalyzingSpringBootProject( detectEventHubs(azdProject, &springBootProject) detectStorageAccount(azdProject, &springBootProject) detectSpringCloudAzure(azdProject, &springBootProject) + detectSpringCloudEureka(azdProject, &springBootProject) + detectSpringCloudConfig(azdProject, &springBootProject) } func detectDatabases(azdProject *Project, springBootProject *SpringBootProject) { @@ -249,6 +251,38 @@ func detectSpringCloudAzure(azdProject *Project, springBootProject *SpringBootPr } } +func detectSpringCloudEureka(azdProject *Project, springBootProject *SpringBootProject) { + var targetGroupId = "org.springframework.cloud" + var targetArtifactId = "spring-cloud-starter-netflix-eureka-server" + if hasDependency(springBootProject, targetGroupId, targetArtifactId) { + azdProject.Dependencies = append(azdProject.Dependencies, EurekaServer) + logServiceAddedAccordingToMavenDependency(EurekaServer.Display(), targetGroupId, targetArtifactId) + } + + targetGroupId = "org.springframework.cloud" + targetArtifactId = "spring-cloud-starter-netflix-eureka-client" + if hasDependency(springBootProject, targetGroupId, targetArtifactId) { + azdProject.Dependencies = append(azdProject.Dependencies, EurekaClient) + logServiceAddedAccordingToMavenDependency(EurekaClient.Display(), targetGroupId, targetArtifactId) + } +} + +func detectSpringCloudConfig(azdProject *Project, springBootProject *SpringBootProject) { + var targetGroupId = "org.springframework.cloud" + var targetArtifactId = "spring-cloud-config-server" + if hasDependency(springBootProject, targetGroupId, targetArtifactId) { + azdProject.Dependencies = append(azdProject.Dependencies, ConfigServer) + logServiceAddedAccordingToMavenDependency(ConfigServer.Display(), targetGroupId, targetArtifactId) + } + + targetGroupId = "org.springframework.cloud" + targetArtifactId = "spring-cloud-starter-config" + if hasDependency(springBootProject, targetGroupId, targetArtifactId) { + azdProject.Dependencies = append(azdProject.Dependencies, ConfigClient) + logServiceAddedAccordingToMavenDependency(ConfigClient.Display(), targetGroupId, targetArtifactId) + } +} + func logServiceAddedAccordingToMavenDependency(resourceName, groupId string, artifactId string) { logServiceAddedAccordingToMavenDependencyAndExtraCondition(resourceName, groupId, artifactId, "") } @@ -267,14 +301,19 @@ func logServiceAddedAccordingToMavenDependencyAndExtraCondition( func detectSpringBootVersion(currentRoot *mavenProject, mavenProject *mavenProject) string { // mavenProject prioritize than rootProject if mavenProject != nil { - return detectSpringBootVersionFromProject(mavenProject) - } else if currentRoot != nil { + if version := detectSpringBootVersionFromProject(mavenProject); version != UnknownSpringBootVersion { + return version + } + } + // fallback to detect root project + if currentRoot != nil { return detectSpringBootVersionFromProject(currentRoot) } return UnknownSpringBootVersion } func detectSpringBootVersionFromProject(project *mavenProject) string { + if project.Parent.ArtifactId == "spring-boot-starter-parent" { return depVersion(project.Parent.Version, project.Properties) } else { diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index b080c252e07..be72711f743 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -680,6 +680,19 @@ func (i *Initializer) prjConfigFromDetect( } } + var eurekaServerService appdetect.Project + var configServerService appdetect.Project + for _, svc := range detect.Services { + for _, dep := range svc.Dependencies { + switch dep { + case appdetect.EurekaServer: + eurekaServerService = svc + case appdetect.ConfigServer: + configServerService = svc + } + } + } + backends := []*project.ResourceConfig{} frontends := []*project.ResourceConfig{} @@ -723,6 +736,21 @@ func (i *Initializer) prjConfigFromDetect( } } + for _, dep := range svc.Dependencies { + switch dep { + case appdetect.EurekaClient: + props.DependsOn = append(props.DependsOn, project.DependsOn{ + ServiceName: svcMapping[eurekaServerService.Path], + DependsType: appdetect.EurekaServer.Display(), + }) + case appdetect.ConfigClient: + props.DependsOn = append(props.DependsOn, project.DependsOn{ + ServiceName: svcMapping[configServerService.Path], + DependsType: appdetect.ConfigServer.Display(), + }) + } + } + resSpec.Name = name resSpec.Props = props config.Resources[name] = &resSpec diff --git a/cli/azd/internal/repository/infra_confirm.go b/cli/azd/internal/repository/infra_confirm.go index 66ba9afdc79..ce805de09e2 100644 --- a/cli/azd/internal/repository/infra_confirm.go +++ b/cli/azd/internal/repository/infra_confirm.go @@ -274,6 +274,14 @@ func PromptPort( svc appdetect.Project) (int, error) { if svc.Docker == nil || svc.Docker.Path == "" { // using default builder from azd if svc.Language == appdetect.Java { + for _, dep := range svc.Dependencies { + switch dep { + case appdetect.EurekaServer: + return 8761, nil + case appdetect.ConfigServer: + return 8888, nil + } + } return 8080, nil } return 80, nil diff --git a/cli/azd/internal/scaffold/spec.go b/cli/azd/internal/scaffold/spec.go index e1c5aa2c597..5c4ccfbc63e 100644 --- a/cli/azd/internal/scaffold/spec.go +++ b/cli/azd/internal/scaffold/spec.go @@ -95,8 +95,9 @@ type AzureDepStorageAccount struct { } type ServiceSpec struct { - Name string - Port int + Name string + Port int + DependsOn []DependsOn Env map[string]string @@ -137,6 +138,11 @@ type AIModelReference struct { Name string } +type DependsOn struct { + ServiceName string `yaml:"serviceName,omitempty"` + DependsType string `yaml:"dependsType,omitempty"` +} + func containerAppExistsParameter(serviceName string) Parameter { return Parameter{ Name: BicepName(serviceName) + "Exists", diff --git a/cli/azd/pkg/project/resources.go b/cli/azd/pkg/project/resources.go index 1eb336d6b1b..3ae6ba6ca61 100644 --- a/cli/azd/pkg/project/resources.go +++ b/cli/azd/pkg/project/resources.go @@ -242,8 +242,9 @@ func (r *ResourceConfig) UnmarshalYAML(value *yaml.Node) error { } type ContainerAppProps struct { - Port int `yaml:"port,omitempty"` - Env []ServiceEnvVar `yaml:"env,omitempty"` + Port int `yaml:"port,omitempty"` + Env []ServiceEnvVar `yaml:"env,omitempty"` + DependsOn []DependsOn `yaml:"dependsOn,omitempty"` } type ServiceEnvVar struct { @@ -254,6 +255,11 @@ type ServiceEnvVar struct { Secret string `yaml:"secret,omitempty"` } +type DependsOn struct { + ServiceName string `yaml:"serviceName,omitempty"` + DependsType string `yaml:"dependsType,omitempty"` +} + type AIModelProps struct { Model AIModelPropsModel `yaml:"model,omitempty"` } diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index 479e9ab8c9c..e8cc50fce87 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -322,7 +322,7 @@ func printHintsAboutUses(infraSpec *scaffold.InfraSpec, projectConfig *ProjectCo return err } case ResourceTypeDbMySQL: - err := printHintsAboutUseMySql(userSpec.DbPostgres.AuthType, console, context) + err := printHintsAboutUseMySql(userSpec.DbMySql.AuthType, console, context) if err != nil { return err } @@ -407,6 +407,12 @@ func handleContainerAppProps( } serviceSpec.Port = port + for _, dependsOn := range props.DependsOn { + serviceSpec.DependsOn = append(serviceSpec.DependsOn, scaffold.DependsOn{ + ServiceName: dependsOn.ServiceName, + DependsType: dependsOn.DependsType, + }) + } return nil } diff --git a/cli/azd/resources/scaffold/templates/resources.bicept b/cli/azd/resources/scaffold/templates/resources.bicept index b99bc3c8c7c..f986518588d 100644 --- a/cli/azd/resources/scaffold/templates/resources.bicept +++ b/cli/azd/resources/scaffold/templates/resources.bicept @@ -932,6 +932,21 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { } {{- end}} + {{- range .DependsOn}} + {{- if (eq .DependsType "EurekaServer") }} + { + name: 'eureka.client.serviceUrl.defaultZone' + value: 'http://{{bicepName .ServiceName}}.outputs.name:8761/eureka/' + } + {{- end}} + {{- if (eq .DependsType "ConfigServer") }} + { + name: 'spring.config.import' + value: 'http://{{bicepName .ServiceName}}.outputs.name:8888' + } + {{- end}} + {{- end}} + {{- if .Frontend}} {{- range $i, $e := .Frontend.Backends}} { diff --git a/schemas/alpha/azure.yaml.json b/schemas/alpha/azure.yaml.json index 2c5cd04d067..dc473e950e9 100644 --- a/schemas/alpha/azure.yaml.json +++ b/schemas/alpha/azure.yaml.json @@ -1277,6 +1277,27 @@ } } } + }, + "dependsOn": { + "type": "array", + "title": "Other services that this service depends on", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "serviceName": { + "type": "string", + "title": "Service Name", + "description": "The name of the service." + }, + "dependsType": { + "type": "string", + "title": "Depend Type", + "description": "The type of depends on." + } + } + }, + "uniqueItems": true } } }, From 5ce87dd7f0736a8f1480e095e1a73f171b98adb3 Mon Sep 17 00:00:00 2001 From: haozhang Date: Tue, 26 Nov 2024 15:57:46 +0800 Subject: [PATCH 02/19] when multi-modules, add Dockerfile and prebuild hooks --- cli/azd/internal/appdetect/appdetect.go | 4 +-- cli/azd/internal/appdetect/docker.go | 7 +++-- cli/azd/internal/appdetect/java.go | 35 ++++++++++++++++++++++++- cli/azd/internal/repository/app_init.go | 20 ++++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/cli/azd/internal/appdetect/appdetect.go b/cli/azd/internal/appdetect/appdetect.go index d920ec8ae5b..269263e5d4a 100644 --- a/cli/azd/internal/appdetect/appdetect.go +++ b/cli/azd/internal/appdetect/appdetect.go @@ -325,8 +325,8 @@ func detectAny(ctx context.Context, detectors []projectDetector, path string, en if project != nil { log.Printf("Found project %s at %s", project.Language, path) - // docker is an optional property of a project, and thus is different than other detectors - docker, err := detectDockerInDirectory(path, entries) + // docker is an optional property of a project, and thus is different from other detectors + docker, err := detectDockerInDirectory(path) if err != nil { return nil, fmt.Errorf("detecting docker project: %w", err) } diff --git a/cli/azd/internal/appdetect/docker.go b/cli/azd/internal/appdetect/docker.go index 69ef7d6a149..420613d21ee 100644 --- a/cli/azd/internal/appdetect/docker.go +++ b/cli/azd/internal/appdetect/docker.go @@ -3,7 +3,6 @@ package appdetect import ( "bufio" "fmt" - "io/fs" "log" "os" "path/filepath" @@ -11,7 +10,11 @@ import ( "strings" ) -func detectDockerInDirectory(path string, entries []fs.DirEntry) (*Docker, error) { +func detectDockerInDirectory(path string) (*Docker, error) { + entries, err := os.ReadDir(path) + if err != nil { + return nil, fmt.Errorf("reading directory: %w", err) + } for _, entry := range entries { if strings.ToLower(entry.Name()) == "dockerfile" { dockerFilePath := filepath.Join(path, entry.Name()) diff --git a/cli/azd/internal/appdetect/java.go b/cli/azd/internal/appdetect/java.go index c15a895dd13..1ad54be5375 100644 --- a/cli/azd/internal/appdetect/java.go +++ b/cli/azd/internal/appdetect/java.go @@ -45,10 +45,13 @@ func (jd *javaDetector) DetectProject(ctx context.Context, path string, entries // we can say that the project is in the root project if the path is under the project if inRoot := strings.HasPrefix(pomFile, rootProject.path); inRoot { currentRoot = &rootProject + err := addDockerfileUnderProject(path) + if err != nil { + return nil, err + } } } - _ = currentRoot // use currentRoot here in the analysis result, err := detectDependencies(currentRoot, project, &Project{ Language: Java, Path: path, @@ -137,6 +140,36 @@ func readMavenProject(filePath string) (*mavenProject, error) { return &project, nil } +func addDockerfileUnderProject(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return fmt.Errorf("path does not exist: %s", path) + } + + dockerfilePath := filepath.Join(path, "Dockerfile") + if _, err := os.Stat(dockerfilePath); err == nil { + fmt.Println("Dockerfile already exists, skipping creation.") + return nil + } + + dockerfileContent := `ARG JAVA_VERSION=17 +FROM mcr.microsoft.com/openjdk/jdk:${JAVA_VERSION}-distroless +COPY ./target/*.jar app.jar +ENTRYPOINT ["java", "-jar", "/app.jar"]` + + file, err := os.Create(dockerfilePath) + if err != nil { + return fmt.Errorf("failed to create Dockerfile: %w", err) + } + defer file.Close() + + _, err = file.WriteString(dockerfileContent) + if err != nil { + return fmt.Errorf("failed to write to Dockerfile: %w", err) + } + + return nil +} + func detectDependencies(currentRoot *mavenProject, mavenProject *mavenProject, project *Project) (*Project, error) { detectAzureDependenciesByAnalyzingSpringBootProject(currentRoot, mavenProject, project) return project, nil diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index be72711f743..361574a0df3 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/azure/azure-dev/cli/azd/pkg/ext" "maps" "os" "path/filepath" @@ -434,6 +435,7 @@ func (i *Initializer) prjConfigFromDetect( }, Services: map[string]*project.ServiceConfig{}, Resources: map[string]*project.ResourceConfig{}, + Hooks: map[string][]*ext.HookConfig{}, } svcMapping := map[string]string{} @@ -535,6 +537,24 @@ func (i *Initializer) prjConfigFromDetect( } } + if prj.Language == appdetect.Java && prj.Docker != nil && prj.Docker.Path != "" { + hookConfig := ext.HookConfig{ + Posix: &ext.HookConfig{ + Shell: ext.ShellTypeBash, + Run: "mvn clean package -DskipTests", + }, + Windows: &ext.HookConfig{ + Shell: ext.ShellTypePowershell, + Run: "mvn clean package -DskipTests", + }, + } + svc.Hooks = project.HooksConfig{ + "prebuild": { + &hookConfig, + }, + } + } + config.Services[svc.Name] = &svc svcMapping[prj.Path] = svc.Name } From ca22c11bf3f2d7efdff93a509c8295ada4c91ffa Mon Sep 17 00:00:00 2001 From: haozhang Date: Tue, 26 Nov 2024 15:58:15 +0800 Subject: [PATCH 03/19] not all services need service connector --- cli/azd/resources/scaffold/templates/resources.bicept | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/resources/scaffold/templates/resources.bicept b/cli/azd/resources/scaffold/templates/resources.bicept index f986518588d..47f27fcfd6b 100644 --- a/cli/azd/resources/scaffold/templates/resources.bicept +++ b/cli/azd/resources/scaffold/templates/resources.bicept @@ -271,8 +271,8 @@ module connectionCreatorIdentity 'br/public:avm/res/managed-identity/user-assign } } {{- end}} -{{- if (and .DbPostgres (eq .DbPostgres.AuthType "USER_ASSIGNED_MANAGED_IDENTITY")) }} {{- range .Services}} +{{- if (and .DbPostgres (eq .DbPostgres.AuthType "USER_ASSIGNED_MANAGED_IDENTITY")) }} module {{bicepName .Name}}CreateConnectionToPostgreSql 'br/public:avm/res/resources/deployment-script:0.4.0' = { name: '{{bicepName .Name}}CreateConnectionToPostgreSql' params: { @@ -292,8 +292,8 @@ module {{bicepName .Name}}CreateConnectionToPostgreSql 'br/public:avm/res/resour } {{- end}} {{- end}} -{{- if (and .DbMySql (eq .DbMySql.AuthType "USER_ASSIGNED_MANAGED_IDENTITY")) }} {{- range .Services}} +{{- if (and .DbMySql (eq .DbMySql.AuthType "USER_ASSIGNED_MANAGED_IDENTITY")) }} module {{bicepName .Name}}CreateConnectionToMysql 'br/public:avm/res/resources/deployment-script:0.4.0' = { name: '{{bicepName .Name}}CreateConnectionToMysql' params: { From 9e31521843d3fba46d31b7dc80928c0c378ecfe9 Mon Sep 17 00:00:00 2001 From: haozhang Date: Wed, 27 Nov 2024 10:01:23 +0800 Subject: [PATCH 04/19] fix Env Var injection for eureka client and config client --- cli/azd/internal/appdetect/spring_boot.go | 1 - .../scaffold/templates/resources.bicept | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cli/azd/internal/appdetect/spring_boot.go b/cli/azd/internal/appdetect/spring_boot.go index ff00e9a41d8..6a89acd3ac6 100644 --- a/cli/azd/internal/appdetect/spring_boot.go +++ b/cli/azd/internal/appdetect/spring_boot.go @@ -313,7 +313,6 @@ func detectSpringBootVersion(currentRoot *mavenProject, mavenProject *mavenProje } func detectSpringBootVersionFromProject(project *mavenProject) string { - if project.Parent.ArtifactId == "spring-boot-starter-parent" { return depVersion(project.Parent.Version, project.Properties) } else { diff --git a/cli/azd/resources/scaffold/templates/resources.bicept b/cli/azd/resources/scaffold/templates/resources.bicept index 47f27fcfd6b..f21c485c4f9 100644 --- a/cli/azd/resources/scaffold/templates/resources.bicept +++ b/cli/azd/resources/scaffold/templates/resources.bicept @@ -936,13 +936,25 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { {{- if (eq .DependsType "EurekaServer") }} { name: 'eureka.client.serviceUrl.defaultZone' - value: 'http://{{bicepName .ServiceName}}.outputs.name:8761/eureka/' + value: 'http://{{bicepName .ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}/eureka/' + } + { + name: 'eureka.client.register-with-eureka' + value: 'true' + } + { + name: 'eureka.client.fetch-registry' + value: 'true' + } + { + name: 'eureka.instance.prefer-ip-address' + value: 'true' } {{- end}} {{- if (eq .DependsType "ConfigServer") }} { name: 'spring.config.import' - value: 'http://{{bicepName .ServiceName}}.outputs.name:8888' + value: 'optional:configserver:https://{{bicepName .ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}' } {{- end}} {{- end}} From 3749bad8060a3254d574c23f6647a903f46355ed Mon Sep 17 00:00:00 2001 From: haozhang Date: Wed, 27 Nov 2024 10:43:05 +0800 Subject: [PATCH 05/19] small fix --- cli/azd/resources/scaffold/templates/resources.bicept | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/resources/scaffold/templates/resources.bicept b/cli/azd/resources/scaffold/templates/resources.bicept index f21c485c4f9..0dae0730a6e 100644 --- a/cli/azd/resources/scaffold/templates/resources.bicept +++ b/cli/azd/resources/scaffold/templates/resources.bicept @@ -936,7 +936,7 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { {{- if (eq .DependsType "EurekaServer") }} { name: 'eureka.client.serviceUrl.defaultZone' - value: 'http://{{bicepName .ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}/eureka/' + value: 'http://{{.ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}/eureka/' } { name: 'eureka.client.register-with-eureka' @@ -954,7 +954,7 @@ module {{bicepName .Name}} 'br/public:avm/res/app/container-app:0.8.0' = { {{- if (eq .DependsType "ConfigServer") }} { name: 'spring.config.import' - value: 'optional:configserver:https://{{bicepName .ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}' + value: 'optional:configserver:https://{{.ServiceName}}.${containerAppsEnvironment.outputs.defaultDomain}' } {{- end}} {{- end}} From 98a1200a67235bf64b5a0596b6b176daf8dbacc6 Mon Sep 17 00:00:00 2001 From: haozhang Date: Thu, 28 Nov 2024 13:42:21 +0800 Subject: [PATCH 06/19] pack build multi-module --- cli/azd/internal/appdetect/appdetect.go | 2 +- cli/azd/internal/appdetect/docker.go | 7 ++-- cli/azd/internal/appdetect/java.go | 34 ------------------- .../pkg/project/framework_service_docker.go | 13 ++++++- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/cli/azd/internal/appdetect/appdetect.go b/cli/azd/internal/appdetect/appdetect.go index 269263e5d4a..cd9c3d0ae24 100644 --- a/cli/azd/internal/appdetect/appdetect.go +++ b/cli/azd/internal/appdetect/appdetect.go @@ -326,7 +326,7 @@ func detectAny(ctx context.Context, detectors []projectDetector, path string, en log.Printf("Found project %s at %s", project.Language, path) // docker is an optional property of a project, and thus is different from other detectors - docker, err := detectDockerInDirectory(path) + docker, err := detectDockerInDirectory(path, entries) if err != nil { return nil, fmt.Errorf("detecting docker project: %w", err) } diff --git a/cli/azd/internal/appdetect/docker.go b/cli/azd/internal/appdetect/docker.go index 420613d21ee..69ef7d6a149 100644 --- a/cli/azd/internal/appdetect/docker.go +++ b/cli/azd/internal/appdetect/docker.go @@ -3,6 +3,7 @@ package appdetect import ( "bufio" "fmt" + "io/fs" "log" "os" "path/filepath" @@ -10,11 +11,7 @@ import ( "strings" ) -func detectDockerInDirectory(path string) (*Docker, error) { - entries, err := os.ReadDir(path) - if err != nil { - return nil, fmt.Errorf("reading directory: %w", err) - } +func detectDockerInDirectory(path string, entries []fs.DirEntry) (*Docker, error) { for _, entry := range entries { if strings.ToLower(entry.Name()) == "dockerfile" { dockerFilePath := filepath.Join(path, entry.Name()) diff --git a/cli/azd/internal/appdetect/java.go b/cli/azd/internal/appdetect/java.go index d1a3fea8290..66cf6febb98 100644 --- a/cli/azd/internal/appdetect/java.go +++ b/cli/azd/internal/appdetect/java.go @@ -47,10 +47,6 @@ func (jd *javaDetector) DetectProject(ctx context.Context, path string, entries // we can say that the project is in the root project if the path is under the project if inRoot := strings.HasPrefix(pomFile, rootProject.path); inRoot { currentRoot = &rootProject - err := addDockerfileUnderProject(path) - if err != nil { - return nil, err - } } } @@ -164,36 +160,6 @@ func replaceAllPlaceholders(project mavenProject, input string) string { }) } -func addDockerfileUnderProject(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { - return fmt.Errorf("path does not exist: %s", path) - } - - dockerfilePath := filepath.Join(path, "Dockerfile") - if _, err := os.Stat(dockerfilePath); err == nil { - fmt.Println("Dockerfile already exists, skipping creation.") - return nil - } - - dockerfileContent := `ARG JAVA_VERSION=17 -FROM mcr.microsoft.com/openjdk/jdk:${JAVA_VERSION}-distroless -COPY ./target/*.jar app.jar -ENTRYPOINT ["java", "-jar", "/app.jar"]` - - file, err := os.Create(dockerfilePath) - if err != nil { - return fmt.Errorf("failed to create Dockerfile: %w", err) - } - defer file.Close() - - _, err = file.WriteString(dockerfileContent) - if err != nil { - return fmt.Errorf("failed to write to Dockerfile: %w", err) - } - - return nil -} - func parseProperties(properties Properties) map[string]string { result := make(map[string]string) for _, entry := range properties.Entries { diff --git a/cli/azd/pkg/project/framework_service_docker.go b/cli/azd/pkg/project/framework_service_docker.go index 944f442107a..97bd4598569 100644 --- a/cli/azd/pkg/project/framework_service_docker.go +++ b/cli/azd/pkg/project/framework_service_docker.go @@ -420,6 +420,7 @@ func (p *dockerProject) packBuild( } builder := DefaultBuilderImage + buildDir := svc.Path() environ := []string{} userDefinedImage := false if os.Getenv("AZD_BUILDER_IMAGE") != "" { @@ -433,6 +434,16 @@ func (p *dockerProject) packBuild( if svc.Language == ServiceLanguageJava { environ = append(environ, "ORYX_RUNTIME_PORT=8080") + // Consider it as multi-module project if service path is not same as its project path + // For multi-module project, specify parent directory and module for pack build + if svc.Path() != svc.Project.Path { + buildDir = svc.Project.Path + svcRelPath, err := filepath.Rel(svc.Project.Path, svc.Path()) + if err != nil { + return nil, err + } + environ = append(environ, fmt.Sprintf("BP_MAVEN_BUILT_MODULE=%s", svcRelPath)) + } } if svc.OutputPath != "" && (svc.Language == ServiceLanguageTypeScript || svc.Language == ServiceLanguageJavaScript) { @@ -491,7 +502,7 @@ func (p *dockerProject) packBuild( err = packCli.Build( ctx, - svc.Path(), + buildDir, builder, imageName, environ, From a72e1a23a8480cc117cf37de2651a886c3c25eab Mon Sep 17 00:00:00 2001 From: haozhang Date: Fri, 29 Nov 2024 14:37:45 +0800 Subject: [PATCH 07/19] commit --- cli/azd/internal/appdetect/appdetect.go | 24 ++-- cli/azd/internal/appdetect/spring_boot.go | 16 +-- .../internal/appdetect/spring_boot_test.go | 101 ++++------------ cli/azd/internal/repository/app_init.go | 112 +++++++++++------- cli/azd/internal/repository/infra_confirm.go | 4 +- cli/azd/internal/scaffold/spec.go | 10 +- cli/azd/pkg/project/resources.go | 10 +- cli/azd/pkg/project/scaffold_gen.go | 26 ++-- cli/azd/pkg/project/scaffold_gen_util.go | 2 +- cli/azd/pkg/project/service_config.go | 2 + schemas/alpha/azure.yaml.json | 29 ++--- 11 files changed, 143 insertions(+), 193 deletions(-) diff --git a/cli/azd/internal/appdetect/appdetect.go b/cli/azd/internal/appdetect/appdetect.go index cd9c3d0ae24..89b634647e9 100644 --- a/cli/azd/internal/appdetect/appdetect.go +++ b/cli/azd/internal/appdetect/appdetect.go @@ -60,10 +60,10 @@ const ( PyDjango Dependency = "django" PyFastApi Dependency = "fastapi" - EurekaServer Dependency = "eureka-server" - EurekaClient Dependency = "eureka-client" - ConfigServer Dependency = "config-server" - ConfigClient Dependency = "config-client" + JavaEurekaServer Dependency = "eureka-server" + JavaEurekaClient Dependency = "eureka-client" + JavaConfigServer Dependency = "config-server" + JavaConfigClient Dependency = "config-client" ) var WebUIFrameworks = map[Dependency]struct{}{ @@ -94,14 +94,14 @@ func (f Dependency) Display() string { return "Vite" case JsNext: return "Next.js" - case EurekaServer: - return "EurekaServer" - case EurekaClient: - return "EurekaClient" - case ConfigServer: - return "ConfigServer" - case ConfigClient: - return "ConfigClient" + case JavaEurekaServer: + return "JavaEurekaServer" + case JavaEurekaClient: + return "JavaEurekaClient" + case JavaConfigServer: + return "JavaConfigServer" + case JavaConfigClient: + return "JavaConfigClient" } return "" diff --git a/cli/azd/internal/appdetect/spring_boot.go b/cli/azd/internal/appdetect/spring_boot.go index 581403b2369..23897bf749c 100644 --- a/cli/azd/internal/appdetect/spring_boot.go +++ b/cli/azd/internal/appdetect/spring_boot.go @@ -255,15 +255,15 @@ func detectSpringCloudEureka(azdProject *Project, springBootProject *SpringBootP var targetGroupId = "org.springframework.cloud" var targetArtifactId = "spring-cloud-starter-netflix-eureka-server" if hasDependency(springBootProject, targetGroupId, targetArtifactId) { - azdProject.Dependencies = append(azdProject.Dependencies, EurekaServer) - logServiceAddedAccordingToMavenDependency(EurekaServer.Display(), targetGroupId, targetArtifactId) + azdProject.Dependencies = append(azdProject.Dependencies, JavaEurekaServer) + logServiceAddedAccordingToMavenDependency(JavaEurekaServer.Display(), targetGroupId, targetArtifactId) } targetGroupId = "org.springframework.cloud" targetArtifactId = "spring-cloud-starter-netflix-eureka-client" if hasDependency(springBootProject, targetGroupId, targetArtifactId) { - azdProject.Dependencies = append(azdProject.Dependencies, EurekaClient) - logServiceAddedAccordingToMavenDependency(EurekaClient.Display(), targetGroupId, targetArtifactId) + azdProject.Dependencies = append(azdProject.Dependencies, JavaEurekaClient) + logServiceAddedAccordingToMavenDependency(JavaEurekaClient.Display(), targetGroupId, targetArtifactId) } } @@ -271,15 +271,15 @@ func detectSpringCloudConfig(azdProject *Project, springBootProject *SpringBootP var targetGroupId = "org.springframework.cloud" var targetArtifactId = "spring-cloud-config-server" if hasDependency(springBootProject, targetGroupId, targetArtifactId) { - azdProject.Dependencies = append(azdProject.Dependencies, ConfigServer) - logServiceAddedAccordingToMavenDependency(ConfigServer.Display(), targetGroupId, targetArtifactId) + azdProject.Dependencies = append(azdProject.Dependencies, JavaConfigServer) + logServiceAddedAccordingToMavenDependency(JavaConfigServer.Display(), targetGroupId, targetArtifactId) } targetGroupId = "org.springframework.cloud" targetArtifactId = "spring-cloud-starter-config" if hasDependency(springBootProject, targetGroupId, targetArtifactId) { - azdProject.Dependencies = append(azdProject.Dependencies, ConfigClient) - logServiceAddedAccordingToMavenDependency(ConfigClient.Display(), targetGroupId, targetArtifactId) + azdProject.Dependencies = append(azdProject.Dependencies, JavaConfigClient) + logServiceAddedAccordingToMavenDependency(JavaConfigClient.Display(), targetGroupId, targetArtifactId) } } diff --git a/cli/azd/internal/appdetect/spring_boot_test.go b/cli/azd/internal/appdetect/spring_boot_test.go index 35a3e6be47f..0b62545c585 100644 --- a/cli/azd/internal/appdetect/spring_boot_test.go +++ b/cli/azd/internal/appdetect/spring_boot_test.go @@ -1,7 +1,6 @@ package appdetect import ( - "encoding/xml" "github.com/stretchr/testify/assert" "testing" ) @@ -47,32 +46,6 @@ func TestDetectSpringBootVersion(t *testing.T) { }, "2.x", }, - { - "project.dependencyManagement.property", - nil, - &mavenProject{ - Properties: Properties{ - Entries: []Property{ - { - XMLName: xml.Name{ - Local: "version.spring.boot", - }, - Value: "2.x", - }, - }, - }, - DependencyManagement: dependencyManagement{ - Dependencies: []dependency{ - { - GroupId: "org.springframework.boot", - ArtifactId: "spring-boot-dependencies", - Version: "${version.spring.boot}", - }, - }, - }, - }, - "2.x", - }, { "root.parent", &mavenProject{ @@ -101,32 +74,6 @@ func TestDetectSpringBootVersion(t *testing.T) { nil, "3.x", }, - { - "root.dependencyManagement.property", - nil, - &mavenProject{ - Properties: Properties{ - Entries: []Property{ - { - XMLName: xml.Name{ - Local: "version.spring.boot", - }, - Value: "3.x", - }, - }, - }, - DependencyManagement: dependencyManagement{ - Dependencies: []dependency{ - { - GroupId: "org.springframework.boot", - ArtifactId: "spring-boot-dependencies", - Version: "${version.spring.boot}", - }, - }, - }, - }, - "3.x", - }, { "both.root.and.project.parent", &mavenProject{ @@ -172,50 +119,48 @@ func TestDetectSpringBootVersion(t *testing.T) { "3.x", }, { - "both.root.and.project.dependencyManagement.property", + "parent.should.detect.root.when.project.not.found", &mavenProject{ - Properties: Properties{ - Entries: []Property{ - { - XMLName: xml.Name{ - Local: "version.spring.boot", - }, - Value: "2.x", - }, - }, + Parent: parent{ + GroupId: "org.springframework.boot", + ArtifactId: "spring-boot-starter-parent", + Version: "2.x", + }, + }, + &mavenProject{ + Parent: parent{ + GroupId: "org.springframework.test", + ArtifactId: "spring-boot-test-parent", + Version: "3.x", }, + }, + "2.x", + }, + { + "dependencyManagement.should.detect.root.when.project.not.found", + &mavenProject{ DependencyManagement: dependencyManagement{ Dependencies: []dependency{ { GroupId: "org.springframework.boot", ArtifactId: "spring-boot-dependencies", - Version: "${version.spring.boot}", + Version: "2.x", }, }, }, }, &mavenProject{ - Properties: Properties{ - Entries: []Property{ - { - XMLName: xml.Name{ - Local: "version.spring.boot", - }, - Value: "3.x", - }, - }, - }, DependencyManagement: dependencyManagement{ Dependencies: []dependency{ { - GroupId: "org.springframework.boot", - ArtifactId: "spring-boot-dependencies", - Version: "${version.spring.boot}", + GroupId: "org.springframework.test", + ArtifactId: "test-dependencies", + Version: "3.x", }, }, }, }, - "3.x", + "2.x", }, } for _, tt := range tests { diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index 361574a0df3..f7ef41a1307 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/azure/azure-dev/cli/azd/pkg/ext" "maps" "os" "path/filepath" @@ -435,7 +434,26 @@ func (i *Initializer) prjConfigFromDetect( }, Services: map[string]*project.ServiceConfig{}, Resources: map[string]*project.ResourceConfig{}, - Hooks: map[string][]*ext.HookConfig{}, + } + + var javaEurekaServerService project.ServiceConfig + var javaConfigServerService project.ServiceConfig + var err error + for _, svc := range detect.Services { + for _, dep := range svc.Dependencies { + switch dep { + case appdetect.JavaEurekaServer: + javaEurekaServerService, err = ServiceFromDetect(root, "", svc) + if err != nil { + return config, err + } + case appdetect.JavaConfigServer: + javaConfigServerService, err = ServiceFromDetect(root, "", svc) + if err != nil { + return config, err + } + } + } } svcMapping := map[string]string{} @@ -537,21 +555,12 @@ func (i *Initializer) prjConfigFromDetect( } } - if prj.Language == appdetect.Java && prj.Docker != nil && prj.Docker.Path != "" { - hookConfig := ext.HookConfig{ - Posix: &ext.HookConfig{ - Shell: ext.ShellTypeBash, - Run: "mvn clean package -DskipTests", - }, - Windows: &ext.HookConfig{ - Shell: ext.ShellTypePowershell, - Run: "mvn clean package -DskipTests", - }, - } - svc.Hooks = project.HooksConfig{ - "prebuild": { - &hookConfig, - }, + for _, dep := range prj.Dependencies { + switch dep { + case appdetect.JavaEurekaClient: + appendJavaEurekaClientEnv(svc, javaEurekaServerService) + case appdetect.JavaConfigClient: + appendJavaConfigClientEnv(svc, javaConfigServerService) } } @@ -700,19 +709,6 @@ func (i *Initializer) prjConfigFromDetect( } } - var eurekaServerService appdetect.Project - var configServerService appdetect.Project - for _, svc := range detect.Services { - for _, dep := range svc.Dependencies { - switch dep { - case appdetect.EurekaServer: - eurekaServerService = svc - case appdetect.ConfigServer: - configServerService = svc - } - } - } - backends := []*project.ResourceConfig{} frontends := []*project.ResourceConfig{} @@ -732,6 +728,35 @@ func (i *Initializer) prjConfigFromDetect( } props.Port = port + for _, dep := range svc.Dependencies { + switch dep { + case appdetect.JavaEurekaClient: + resSpec.Uses = append(resSpec.Uses, javaEurekaServerService.Name) + /*props.Env = append(props.Env, project.ServiceEnvVar{ + Name: "eureka.client.register-with-eureka", + Value: "true", + }) + props.Env = append(props.Env, project.ServiceEnvVar{ + Name: "eureka.client.fetch-registry", + Value: "true", + }) + props.Env = append(props.Env, project.ServiceEnvVar{ + Name: "eureka.instance.prefer-ip-address", + Value: "true", + }) + props.Env = append(props.Env, project.ServiceEnvVar{ + Name: "eureka.client.serviceUrl.defaultZone", + Value: fmt.Sprintf("${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)), + })*/ + case appdetect.JavaConfigClient: + resSpec.Uses = append(resSpec.Uses, javaConfigServerService.Name) + /*props.Env = append(props.Env, project.ServiceEnvVar{ + Name: "spring.config.import", + Value: fmt.Sprintf("optional:configserver:${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)), + })*/ + } + } + for _, db := range svc.DatabaseDeps { // filter out databases that were removed if _, ok := detect.Databases[db]; !ok { @@ -756,21 +781,6 @@ func (i *Initializer) prjConfigFromDetect( } } - for _, dep := range svc.Dependencies { - switch dep { - case appdetect.EurekaClient: - props.DependsOn = append(props.DependsOn, project.DependsOn{ - ServiceName: svcMapping[eurekaServerService.Path], - DependsType: appdetect.EurekaServer.Display(), - }) - case appdetect.ConfigClient: - props.DependsOn = append(props.DependsOn, project.DependsOn{ - ServiceName: svcMapping[configServerService.Path], - DependsType: appdetect.ConfigServer.Display(), - }) - } - } - resSpec.Name = name resSpec.Props = props config.Resources[name] = &resSpec @@ -836,6 +846,7 @@ func ServiceFromDetect( prj appdetect.Project) (project.ServiceConfig, error) { svc := project.ServiceConfig{ Name: svcName, + Env: map[string]string{}, } rel, err := filepath.Rel(root, prj.Path) if err != nil { @@ -956,3 +967,14 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string return appdetect.UnknownSpringBootVersion, nil } } + +func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerService project.ServiceConfig) { + svc.Env["eureka.client.register-with-eureka"] = "true" + svc.Env["eureka.client.fetch-registry"] = "true" + svc.Env["eureka.instance.prefer-ip-address"] = "true" + svc.Env["eureka.client.serviceUrl.defaultZone"] = fmt.Sprintf("\\${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)) +} + +func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig) { + svc.Env["spring.config.import"] = fmt.Sprintf("optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)) +} diff --git a/cli/azd/internal/repository/infra_confirm.go b/cli/azd/internal/repository/infra_confirm.go index ce805de09e2..a78cff2ab64 100644 --- a/cli/azd/internal/repository/infra_confirm.go +++ b/cli/azd/internal/repository/infra_confirm.go @@ -276,9 +276,9 @@ func PromptPort( if svc.Language == appdetect.Java { for _, dep := range svc.Dependencies { switch dep { - case appdetect.EurekaServer: + case appdetect.JavaEurekaServer: return 8761, nil - case appdetect.ConfigServer: + case appdetect.JavaConfigServer: return 8888, nil } } diff --git a/cli/azd/internal/scaffold/spec.go b/cli/azd/internal/scaffold/spec.go index 2af1baa234d..198556c5798 100644 --- a/cli/azd/internal/scaffold/spec.go +++ b/cli/azd/internal/scaffold/spec.go @@ -95,9 +95,8 @@ type AzureDepStorageAccount struct { } type ServiceSpec struct { - Name string - Port int - DependsOn []DependsOn + Name string + Port int Envs []Env @@ -206,11 +205,6 @@ type AIModelReference struct { Name string } -type DependsOn struct { - ServiceName string `yaml:"serviceName,omitempty"` - DependsType string `yaml:"dependsType,omitempty"` -} - func containerAppExistsParameter(serviceName string) Parameter { return Parameter{ Name: BicepName(serviceName) + "Exists", diff --git a/cli/azd/pkg/project/resources.go b/cli/azd/pkg/project/resources.go index 3ae6ba6ca61..1eb336d6b1b 100644 --- a/cli/azd/pkg/project/resources.go +++ b/cli/azd/pkg/project/resources.go @@ -242,9 +242,8 @@ func (r *ResourceConfig) UnmarshalYAML(value *yaml.Node) error { } type ContainerAppProps struct { - Port int `yaml:"port,omitempty"` - Env []ServiceEnvVar `yaml:"env,omitempty"` - DependsOn []DependsOn `yaml:"dependsOn,omitempty"` + Port int `yaml:"port,omitempty"` + Env []ServiceEnvVar `yaml:"env,omitempty"` } type ServiceEnvVar struct { @@ -255,11 +254,6 @@ type ServiceEnvVar struct { Secret string `yaml:"secret,omitempty"` } -type DependsOn struct { - ServiceName string `yaml:"serviceName,omitempty"` - DependsType string `yaml:"dependsType,omitempty"` -} - type AIModelProps struct { Model AIModelPropsModel `yaml:"model,omitempty"` } diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index a3ce0416f95..912c1785fe6 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -206,6 +206,7 @@ func infraSpec(projectConfig *ProjectConfig, if err != nil { return nil, err } + serviceSpec.Envs = append(serviceSpec.Envs, serviceConfigEnv(projectConfig.Services[resource.Name])...) infraSpec.Services = append(infraSpec.Services, serviceSpec) case ResourceTypeOpenAiModel: props := resource.Props.(AIModelProps) @@ -382,7 +383,7 @@ func printEnvListAboutUses(infraSpec *scaffold.InfraSpec, projectConfig *Project } console.Message(ctx, fmt.Sprintf("\nInformation about environment variables:\n"+ "In azure.yaml, '%s' uses '%s'. \n"+ - "The 'uses' relashipship is implemented by environment variables. \n"+ + "The 'uses' relationship is implemented by environment variables. \n"+ "Please make sure your application used the right environment variable. \n"+ "Here is the list of environment variables: ", userResourceName, usedResourceName)) @@ -450,7 +451,6 @@ func handleContainerAppProps( if err != nil { return err } - return nil } port := props.Port @@ -459,12 +459,6 @@ func handleContainerAppProps( } serviceSpec.Port = port - for _, dependsOn := range props.DependsOn { - serviceSpec.DependsOn = append(serviceSpec.DependsOn, scaffold.DependsOn{ - ServiceName: dependsOn.ServiceName, - DependsType: dependsOn.DependsType, - }) - } return nil } @@ -517,9 +511,9 @@ func genBicepParamsFromEnvSubst( if len(names) == 0 { // literal string with no expressions result = s - } else if len(names) == 1 { + /*} else if len(names) == 1 { // single expression, return the bicep parameter name to reference the expression - result = "${" + scaffold.BicepName(names[0]) + "}" + result = "${" + scaffold.BicepName(names[0]) + "}"*/ } else { // multiple expressions // construct the string with all expressions replaced by parameter references as a Bicep interpolated string @@ -533,6 +527,7 @@ func genBicepParamsFromEnvSubst( result += "}" previous = loc.stop + 1 } + result += s[previous:] } return result @@ -577,3 +572,14 @@ func printHintsAboutUseHostContainerApp(userResourceName string, usedResourceNam console.Message(ctx, fmt.Sprintf("Environemnt variables in %s:", usedResourceName)) console.Message(ctx, fmt.Sprintf("%s_BASE_URL=xxx", strings.ToUpper(userResourceName))) } + +func serviceConfigEnv(svcConfig *ServiceConfig) []scaffold.Env { + var envs []scaffold.Env + for key, val := range svcConfig.Env { + envs = append(envs, scaffold.Env{ + Name: key, + Value: val, + }) + } + return envs +} diff --git a/cli/azd/pkg/project/scaffold_gen_util.go b/cli/azd/pkg/project/scaffold_gen_util.go index 26a1934750c..290098eec09 100644 --- a/cli/azd/pkg/project/scaffold_gen_util.go +++ b/cli/azd/pkg/project/scaffold_gen_util.go @@ -38,7 +38,7 @@ func parseEnvSubstVariables(s string) (names []string, expressions []location) { if inVar { if inVarName { // detect the end of the variable name // a variable name can contain letters, digits, and underscores, and nothing else. - if unicode.IsLetter(rune(s[i])) || unicode.IsDigit(rune(s[i])) || s[i] == '_' { + if unicode.IsLetter(rune(s[i])) || unicode.IsDigit(rune(s[i])) || s[i] == '_' || s[i] == '-' { _ = name.WriteByte(s[i]) } else { // a non-matching character means we've reached the end of the name inVarName = false diff --git a/cli/azd/pkg/project/service_config.go b/cli/azd/pkg/project/service_config.go index b1af653b911..7881fbc85df 100644 --- a/cli/azd/pkg/project/service_config.go +++ b/cli/azd/pkg/project/service_config.go @@ -45,6 +45,8 @@ type ServiceConfig struct { DotNetContainerApp *DotNetContainerAppOptions `yaml:"-,omitempty"` // Custom configuration for the service target Config map[string]any `yaml:"config,omitempty"` + // Environment variables for service + Env map[string]string `yaml:"env,omitempty"` *ext.EventDispatcher[ServiceLifecycleEventArgs] `yaml:"-"` } diff --git a/schemas/alpha/azure.yaml.json b/schemas/alpha/azure.yaml.json index dc473e950e9..bb50a0cb0cc 100644 --- a/schemas/alpha/azure.yaml.json +++ b/schemas/alpha/azure.yaml.json @@ -159,6 +159,14 @@ "type": "object", "additionalProperties": true }, + "env": { + "type": "object", + "title": "A map of key value pairs used to set as environment variables for the service.", + "description": "Optional. Supports environment variable substitution.", + "additionalProperties": { + "type": "string" + } + }, "hooks": { "type": "object", "title": "Service level hooks", @@ -1277,27 +1285,6 @@ } } } - }, - "dependsOn": { - "type": "array", - "title": "Other services that this service depends on", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "serviceName": { - "type": "string", - "title": "Service Name", - "description": "The name of the service." - }, - "dependsType": { - "type": "string", - "title": "Depend Type", - "description": "The type of depends on." - } - } - }, - "uniqueItems": true } } }, From 4253d81ee4d088158a54d1210c24b1207d1ad726 Mon Sep 17 00:00:00 2001 From: haozhang Date: Fri, 29 Nov 2024 16:03:16 +0800 Subject: [PATCH 08/19] comment for escape bicep --- cli/azd/internal/repository/app_init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index f7ef41a1307..ca0b1f23685 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -972,6 +972,7 @@ func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerServic svc.Env["eureka.client.register-with-eureka"] = "true" svc.Env["eureka.client.fetch-registry"] = "true" svc.Env["eureka.instance.prefer-ip-address"] = "true" + // Here use '\${}' to escape parsing ${} placeholder variable in Bicep, just treat it as an Env of application svc.Env["eureka.client.serviceUrl.defaultZone"] = fmt.Sprintf("\\${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)) } From 88295b03c90a7026af1d10167d280daaabd71e3d Mon Sep 17 00:00:00 2001 From: haozhang Date: Fri, 29 Nov 2024 16:13:42 +0800 Subject: [PATCH 09/19] simplify some changes --- cli/azd/internal/repository/app_init.go | 20 -------------------- cli/azd/pkg/project/scaffold_gen.go | 5 ++--- cli/azd/pkg/project/scaffold_gen_util.go | 2 +- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index 4b6f2377dda..d7bdd5992b5 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -730,28 +730,8 @@ func (i *Initializer) prjConfigFromDetect( switch dep { case appdetect.JavaEurekaClient: resSpec.Uses = append(resSpec.Uses, javaEurekaServerService.Name) - /*props.Env = append(props.Env, project.ServiceEnvVar{ - Name: "eureka.client.register-with-eureka", - Value: "true", - }) - props.Env = append(props.Env, project.ServiceEnvVar{ - Name: "eureka.client.fetch-registry", - Value: "true", - }) - props.Env = append(props.Env, project.ServiceEnvVar{ - Name: "eureka.instance.prefer-ip-address", - Value: "true", - }) - props.Env = append(props.Env, project.ServiceEnvVar{ - Name: "eureka.client.serviceUrl.defaultZone", - Value: fmt.Sprintf("${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)), - })*/ case appdetect.JavaConfigClient: resSpec.Uses = append(resSpec.Uses, javaConfigServerService.Name) - /*props.Env = append(props.Env, project.ServiceEnvVar{ - Name: "spring.config.import", - Value: fmt.Sprintf("optional:configserver:${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)), - })*/ } } diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index 7bee250d96f..a54872ff3e8 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -511,9 +511,9 @@ func genBicepParamsFromEnvSubst( if len(names) == 0 { // literal string with no expressions result = s - /*} else if len(names) == 1 { + } else if len(names) == 1 { // single expression, return the bicep parameter name to reference the expression - result = "${" + scaffold.BicepName(names[0]) + "}"*/ + result = "${" + scaffold.BicepName(names[0]) + "}" } else { // multiple expressions // construct the string with all expressions replaced by parameter references as a Bicep interpolated string @@ -527,7 +527,6 @@ func genBicepParamsFromEnvSubst( result += "}" previous = loc.stop + 1 } - result += s[previous:] } return result diff --git a/cli/azd/pkg/project/scaffold_gen_util.go b/cli/azd/pkg/project/scaffold_gen_util.go index 290098eec09..26a1934750c 100644 --- a/cli/azd/pkg/project/scaffold_gen_util.go +++ b/cli/azd/pkg/project/scaffold_gen_util.go @@ -38,7 +38,7 @@ func parseEnvSubstVariables(s string) (names []string, expressions []location) { if inVar { if inVarName { // detect the end of the variable name // a variable name can contain letters, digits, and underscores, and nothing else. - if unicode.IsLetter(rune(s[i])) || unicode.IsDigit(rune(s[i])) || s[i] == '_' || s[i] == '-' { + if unicode.IsLetter(rune(s[i])) || unicode.IsDigit(rune(s[i])) || s[i] == '_' { _ = name.WriteByte(s[i]) } else { // a non-matching character means we've reached the end of the name inVarName = false From da2017ecb2910313a6cad770b4de45e08ace17ec Mon Sep 17 00:00:00 2001 From: haozhang Date: Fri, 29 Nov 2024 16:26:17 +0800 Subject: [PATCH 10/19] fix --- cli/azd/internal/repository/app_init.go | 8 +++++--- cli/azd/pkg/project/scaffold_gen.go | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index d7bdd5992b5..20d615d4e67 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -952,10 +952,12 @@ func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerServic svc.Env["eureka.client.register-with-eureka"] = "true" svc.Env["eureka.client.fetch-registry"] = "true" svc.Env["eureka.instance.prefer-ip-address"] = "true" - // Here use '\${}' to escape parsing ${} placeholder variable in Bicep, just treat it as an Env of application - svc.Env["eureka.client.serviceUrl.defaultZone"] = fmt.Sprintf("\\${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)) + // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application + svc.Env["eureka.client.serviceUrl.defaultZone"] = fmt.Sprintf( + "\\${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)) } func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig) { - svc.Env["spring.config.import"] = fmt.Sprintf("optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)) + svc.Env["spring.config.import"] = fmt.Sprintf( + "optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)) } diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index a54872ff3e8..091a1687c65 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -574,11 +574,13 @@ func printHintsAboutUseHostContainerApp(userResourceName string, usedResourceNam func serviceConfigEnv(svcConfig *ServiceConfig) []scaffold.Env { var envs []scaffold.Env - for key, val := range svcConfig.Env { - envs = append(envs, scaffold.Env{ - Name: key, - Value: val, - }) + if svcConfig != nil { + for key, val := range svcConfig.Env { + envs = append(envs, scaffold.Env{ + Name: key, + Value: val, + }) + } } return envs } From 5553b65179f486d97788674f170f69d41288ae14 Mon Sep 17 00:00:00 2001 From: haozhang Date: Fri, 29 Nov 2024 16:40:10 +0800 Subject: [PATCH 11/19] small fix nil Env --- cli/azd/internal/repository/app_init.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index 20d615d4e67..cbca0346a67 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -824,7 +824,6 @@ func ServiceFromDetect( prj appdetect.Project) (project.ServiceConfig, error) { svc := project.ServiceConfig{ Name: svcName, - Env: map[string]string{}, } rel, err := filepath.Rel(root, prj.Path) if err != nil { @@ -949,6 +948,9 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string } func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerService project.ServiceConfig) { + if svc.Env == nil { + svc.Env = map[string]string{} + } svc.Env["eureka.client.register-with-eureka"] = "true" svc.Env["eureka.client.fetch-registry"] = "true" svc.Env["eureka.instance.prefer-ip-address"] = "true" @@ -958,6 +960,9 @@ func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerServic } func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig) { + if svc.Env == nil { + svc.Env = map[string]string{} + } svc.Env["spring.config.import"] = fmt.Sprintf( "optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)) } From d87262c30749fedaa57d5db5e2fe8ab644bae2f4 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 10:29:03 +0800 Subject: [PATCH 12/19] inject env by resource type eureka / config server --- cli/azd/internal/repository/app_init.go | 47 ++++++++++++++----- cli/azd/pkg/project/resources.go | 7 +++ cli/azd/pkg/project/scaffold_gen.go | 7 ++- .../scaffold_gen_environment_variables.go | 30 +++++++++++- 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index cbca0346a67..1cd8ae3dc3a 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -558,9 +558,15 @@ func (i *Initializer) prjConfigFromDetect( for _, dep := range prj.Dependencies { switch dep { case appdetect.JavaEurekaClient: - appendJavaEurekaClientEnv(svc, javaEurekaServerService) + err := appendJavaEurekaClientEnv(svc, javaEurekaServerService, spec) + if err != nil { + return config, err + } case appdetect.JavaConfigClient: - appendJavaConfigClientEnv(svc, javaConfigServerService) + err := appendJavaConfigClientEnv(svc, javaConfigServerService, spec) + if err != nil { + return config, err + } } } @@ -947,22 +953,39 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string } } -func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerService project.ServiceConfig) { +func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { svc.Env = map[string]string{} } - svc.Env["eureka.client.register-with-eureka"] = "true" - svc.Env["eureka.client.fetch-registry"] = "true" - svc.Env["eureka.instance.prefer-ip-address"] = "true" - // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application - svc.Env["eureka.client.serviceUrl.defaultZone"] = fmt.Sprintf( - "\\${%s_BASE_URL}/eureka", strings.ToUpper(javaEurekaServerService.Name)) + eurekaClientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ + Name: javaEurekaServerService.Name, + Type: project.ResourceTypeJavaEurekaServer, + }, infraSpec) + if err != nil { + return err + } + + for _, env := range eurekaClientEnvs { + svc.Env[env.Name] = env.Value + } + return nil } -func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig) { +func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { svc.Env = map[string]string{} } - svc.Env["spring.config.import"] = fmt.Sprintf( - "optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(javaConfigServerService.Name)) + + configClientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ + Name: javaConfigServerService.Name, + Type: project.ResourceTypeJavaEurekaServer, + }, infraSpec) + if err != nil { + return err + } + + for _, env := range configClientEnvs { + svc.Env[env.Name] = env.Value + } + return nil } diff --git a/cli/azd/pkg/project/resources.go b/cli/azd/pkg/project/resources.go index 1eb336d6b1b..1d52e07b2c8 100644 --- a/cli/azd/pkg/project/resources.go +++ b/cli/azd/pkg/project/resources.go @@ -34,6 +34,9 @@ const ( ResourceTypeMessagingEventHubs ResourceType = "messaging.eventhubs" ResourceTypeMessagingKafka ResourceType = "messaging.kafka" ResourceTypeStorage ResourceType = "storage" + + ResourceTypeJavaEurekaServer ResourceType = "java.eureka.server" + ResourceTypeJavaConfigServer ResourceType = "java.config.server" ) func (r ResourceType) String() string { @@ -60,6 +63,10 @@ func (r ResourceType) String() string { return "Kafka" case ResourceTypeStorage: return "Storage Account" + case ResourceTypeJavaEurekaServer: + return "Java Eureka Server" + case ResourceTypeJavaConfigServer: + return "Java Config Server" } return "" diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index 091a1687c65..0b56f22c848 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -348,13 +348,16 @@ func getAuthType(infraSpec *scaffold.InfraSpec, resourceType ResourceType) (inte return infraSpec.AzureEventHubs.AuthType, nil case ResourceTypeStorage: return infraSpec.AzureStorageAccount.AuthType, nil + case ResourceTypeJavaEurekaServer, + ResourceTypeJavaConfigServer: + return internal.AuthTypeUnspecified, nil default: return internal.AuthTypeUnspecified, fmt.Errorf("can not get authType, resource type: %s", resourceType) } } func addUsageByEnv(infraSpec *scaffold.InfraSpec, userSpec *scaffold.ServiceSpec, usedResource *ResourceConfig) error { - envs, err := getResourceConnectionEnvs(usedResource, infraSpec) + envs, err := GetResourceConnectionEnvs(usedResource, infraSpec) if err != nil { return err } @@ -397,7 +400,7 @@ func printEnvListAboutUses(infraSpec *scaffold.InfraSpec, projectConfig *Project ResourceTypeMessagingEventHubs, ResourceTypeMessagingKafka, ResourceTypeStorage: - variables, err := getResourceConnectionEnvs(usedResource, infraSpec) + variables, err := GetResourceConnectionEnvs(usedResource, infraSpec) if err != nil { return err } diff --git a/cli/azd/pkg/project/scaffold_gen_environment_variables.go b/cli/azd/pkg/project/scaffold_gen_environment_variables.go index e1224f54fdd..110e11d16d8 100644 --- a/cli/azd/pkg/project/scaffold_gen_environment_variables.go +++ b/cli/azd/pkg/project/scaffold_gen_environment_variables.go @@ -7,7 +7,7 @@ import ( "strings" ) -func getResourceConnectionEnvs(usedResource *ResourceConfig, +func GetResourceConnectionEnvs(usedResource *ResourceConfig, infraSpec *scaffold.InfraSpec) ([]scaffold.Env, error) { resourceType := usedResource.Type authType, err := getAuthType(infraSpec, usedResource.Type) @@ -556,6 +556,34 @@ func getResourceConnectionEnvs(usedResource *ResourceConfig, default: return []scaffold.Env{}, unsupportedAuthTypeError(resourceType, authType) } + case ResourceTypeJavaEurekaServer: + return []scaffold.Env{ + { + Name: "eureka.client.register-with-eureka", + Value: "true", + }, + { + Name: "eureka.client.fetch-registry", + Value: "true", + }, + { + Name: "eureka.instance.prefer-ip-address", + Value: "true", + }, + { + // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application + Name: "eureka.client.serviceUrl.defaultZone", + Value: fmt.Sprintf("\\${%s_BASE_URL}/eureka", strings.ToUpper(usedResource.Name)), + }, + }, nil + case ResourceTypeJavaConfigServer: + return []scaffold.Env{ + { + // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application + Name: "spring.config.import", + Value: fmt.Sprintf("optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(usedResource.Name)), + }, + }, nil default: return []scaffold.Env{}, unsupportedResourceTypeError(resourceType) } From a0e34cf0e8f21e420b3d0d16d563b4c3f1e57b07 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 10:36:27 +0800 Subject: [PATCH 13/19] lint fix --- cli/azd/internal/repository/app_init.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index 1cd8ae3dc3a..a174b66ba04 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -953,7 +953,9 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string } } -func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { +func appendJavaEurekaClientEnv(svc project.ServiceConfig, + javaEurekaServerService project.ServiceConfig, + infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { svc.Env = map[string]string{} } @@ -971,7 +973,9 @@ func appendJavaEurekaClientEnv(svc project.ServiceConfig, javaEurekaServerServic return nil } -func appendJavaConfigClientEnv(svc project.ServiceConfig, javaConfigServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { +func appendJavaConfigClientEnv(svc project.ServiceConfig, + javaConfigServerService project.ServiceConfig, + infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { svc.Env = map[string]string{} } From bf7a198ff88d9c5b8fef892e1121bb4a8eca7111 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 10:47:03 +0800 Subject: [PATCH 14/19] small fix --- cli/azd/internal/repository/app_init.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index a174b66ba04..841346ef0fa 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -558,12 +558,12 @@ func (i *Initializer) prjConfigFromDetect( for _, dep := range prj.Dependencies { switch dep { case appdetect.JavaEurekaClient: - err := appendJavaEurekaClientEnv(svc, javaEurekaServerService, spec) + err := appendJavaEurekaClientEnv(&svc, javaEurekaServerService, spec) if err != nil { return config, err } case appdetect.JavaConfigClient: - err := appendJavaConfigClientEnv(svc, javaConfigServerService, spec) + err := appendJavaConfigClientEnv(&svc, javaConfigServerService, spec) if err != nil { return config, err } @@ -953,7 +953,7 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string } } -func appendJavaEurekaClientEnv(svc project.ServiceConfig, +func appendJavaEurekaClientEnv(svc *project.ServiceConfig, javaEurekaServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { @@ -973,7 +973,7 @@ func appendJavaEurekaClientEnv(svc project.ServiceConfig, return nil } -func appendJavaConfigClientEnv(svc project.ServiceConfig, +func appendJavaConfigClientEnv(svc *project.ServiceConfig, javaConfigServerService project.ServiceConfig, infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { @@ -982,7 +982,7 @@ func appendJavaConfigClientEnv(svc project.ServiceConfig, configClientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ Name: javaConfigServerService.Name, - Type: project.ResourceTypeJavaEurekaServer, + Type: project.ResourceTypeJavaConfigServer, }, infraSpec) if err != nil { return err From 59c094c2419758c34cfc341decfb6154b07e960f Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 14:06:32 +0800 Subject: [PATCH 15/19] process escape logic in backend --- cli/azd/pkg/project/scaffold_gen.go | 5 +++-- cli/azd/pkg/project/scaffold_gen_environment_variables.go | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index 0b56f22c848..d4b10621fe2 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -580,8 +580,9 @@ func serviceConfigEnv(svcConfig *ServiceConfig) []scaffold.Env { if svcConfig != nil { for key, val := range svcConfig.Env { envs = append(envs, scaffold.Env{ - Name: key, - Value: val, + Name: key, + // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an application Env + Value: strings.ReplaceAll(val, "${", "\\${"), }) } } diff --git a/cli/azd/pkg/project/scaffold_gen_environment_variables.go b/cli/azd/pkg/project/scaffold_gen_environment_variables.go index 110e11d16d8..e1611effabe 100644 --- a/cli/azd/pkg/project/scaffold_gen_environment_variables.go +++ b/cli/azd/pkg/project/scaffold_gen_environment_variables.go @@ -571,17 +571,15 @@ func GetResourceConnectionEnvs(usedResource *ResourceConfig, Value: "true", }, { - // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application Name: "eureka.client.serviceUrl.defaultZone", - Value: fmt.Sprintf("\\${%s_BASE_URL}/eureka", strings.ToUpper(usedResource.Name)), + Value: fmt.Sprintf("${%s_BASE_URL}/eureka", strings.ToUpper(usedResource.Name)), }, }, nil case ResourceTypeJavaConfigServer: return []scaffold.Env{ { - // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an Env of application Name: "spring.config.import", - Value: fmt.Sprintf("optional:configserver:\\${%s_BASE_URL}", strings.ToUpper(usedResource.Name)), + Value: fmt.Sprintf("optional:configserver:${%s_BASE_URL}", strings.ToUpper(usedResource.Name)), }, }, nil default: From 7bdc8a098bf64eb52e9efe31255937d80386e1a0 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 14:20:23 +0800 Subject: [PATCH 16/19] add UT --- cli/azd/pkg/project/scaffold_gen_test.go | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cli/azd/pkg/project/scaffold_gen_test.go b/cli/azd/pkg/project/scaffold_gen_test.go index a3c11a38119..22e8bf79195 100644 --- a/cli/azd/pkg/project/scaffold_gen_test.go +++ b/cli/azd/pkg/project/scaffold_gen_test.go @@ -4,6 +4,7 @@ package project import ( + "reflect" "testing" "github.com/azure/azure-dev/cli/azd/internal/scaffold" @@ -74,3 +75,35 @@ func Test_genBicepParamsFromEnvSubst(t *testing.T) { }) } } + +func TestServiceConfigEnvToScaffoldEnv(t *testing.T) { + tests := []struct { + name string + serviceConfig *ServiceConfig + wantScaffoldEnv []scaffold.Env + }{ + { + name: "eureka-and-config-server", + serviceConfig: &ServiceConfig{ + Env: map[string]string{ + "eureka.client.serviceUrl.defaultZone": "${DISCOVERY-SERVER_BASE_URL}/eureka", + "eureka.instance.prefer-ip-address": "true", + "spring.config.import": "optional:configserver:${CONFIG-SERVER_BASE_URL}", + }, + }, + wantScaffoldEnv: []scaffold.Env{ + {Name: "eureka.client.serviceUrl.defaultZone", Value: "\\${DISCOVERY-SERVER_BASE_URL}/eureka"}, + {Name: "eureka.instance.prefer-ip-address", Value: "true"}, + {Name: "spring.config.import", Value: "optional:configserver:\\${CONFIG-SERVER_BASE_URL}"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := serviceConfigEnv(tt.serviceConfig) + if !reflect.DeepEqual(actual, tt.wantScaffoldEnv) { + t.Errorf("serviceConfigEnv() got = %v, want %v", actual, tt.wantScaffoldEnv) + } + }) + } +} From 94b6fe9e791655b5522148343ba7c89a16cd7ed6 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 15:19:02 +0800 Subject: [PATCH 17/19] fix to avoid escape placeholder --- cli/azd/.vscode/cspell.yaml | 1 + cli/azd/internal/scaffold/bicep_env.go | 12 +++++++++++- cli/azd/pkg/project/scaffold_gen.go | 5 ++--- .../project/scaffold_gen_environment_variables.go | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cli/azd/.vscode/cspell.yaml b/cli/azd/.vscode/cspell.yaml index 30e56b1fbf0..d22ab125382 100644 --- a/cli/azd/.vscode/cspell.yaml +++ b/cli/azd/.vscode/cspell.yaml @@ -21,6 +21,7 @@ words: - postgre - mysqladmin - sjad + - configserver languageSettings: - languageId: go ignoreRegExpList: diff --git a/cli/azd/internal/scaffold/bicep_env.go b/cli/azd/internal/scaffold/bicep_env.go index fb410671969..f446a52d1b3 100644 --- a/cli/azd/internal/scaffold/bicep_env.go +++ b/cli/azd/internal/scaffold/bicep_env.go @@ -176,7 +176,17 @@ var bicepEnv = map[ResourceType]map[ResourceInfoType]string{ ResourceTypeOpenAiModel: { ResourceInfoTypeEndpoint: "account.outputs.endpoint", }, - ResourceTypeHostContainerApp: {}, + ResourceTypeHostContainerApp: { + ResourceInfoTypeHost: "https://{{BackendName}}.${containerAppsEnvironment.outputs.defaultDomain}", + }, +} + +func GetContainerAppHost(name string) string { + return strings.ReplaceAll( + bicepEnv[ResourceTypeHostContainerApp][ResourceInfoTypeHost], + "{{BackendName}}", + name, + ) } func unsupportedType(env Env) string { diff --git a/cli/azd/pkg/project/scaffold_gen.go b/cli/azd/pkg/project/scaffold_gen.go index d4b10621fe2..0b56f22c848 100644 --- a/cli/azd/pkg/project/scaffold_gen.go +++ b/cli/azd/pkg/project/scaffold_gen.go @@ -580,9 +580,8 @@ func serviceConfigEnv(svcConfig *ServiceConfig) []scaffold.Env { if svcConfig != nil { for key, val := range svcConfig.Env { envs = append(envs, scaffold.Env{ - Name: key, - // Use '\${}' to escape parsing ${} placeholder in Bicep, just treat it as an application Env - Value: strings.ReplaceAll(val, "${", "\\${"), + Name: key, + Value: val, }) } } diff --git a/cli/azd/pkg/project/scaffold_gen_environment_variables.go b/cli/azd/pkg/project/scaffold_gen_environment_variables.go index e1611effabe..6dd03e02a82 100644 --- a/cli/azd/pkg/project/scaffold_gen_environment_variables.go +++ b/cli/azd/pkg/project/scaffold_gen_environment_variables.go @@ -572,14 +572,14 @@ func GetResourceConnectionEnvs(usedResource *ResourceConfig, }, { Name: "eureka.client.serviceUrl.defaultZone", - Value: fmt.Sprintf("${%s_BASE_URL}/eureka", strings.ToUpper(usedResource.Name)), + Value: fmt.Sprintf("%s/eureka", scaffold.GetContainerAppHost(usedResource.Name)), }, }, nil case ResourceTypeJavaConfigServer: return []scaffold.Env{ { Name: "spring.config.import", - Value: fmt.Sprintf("optional:configserver:${%s_BASE_URL}", strings.ToUpper(usedResource.Name)), + Value: fmt.Sprintf("optional:configserver:%s", scaffold.GetContainerAppHost(usedResource.Name)), }, }, nil default: From 3414f442368ef38dd2c04f9fc2e86c6a42b43364 Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 15:23:59 +0800 Subject: [PATCH 18/19] fix UT --- cli/azd/pkg/project/scaffold_gen_test.go | 33 ------------------------ 1 file changed, 33 deletions(-) diff --git a/cli/azd/pkg/project/scaffold_gen_test.go b/cli/azd/pkg/project/scaffold_gen_test.go index 22e8bf79195..a3c11a38119 100644 --- a/cli/azd/pkg/project/scaffold_gen_test.go +++ b/cli/azd/pkg/project/scaffold_gen_test.go @@ -4,7 +4,6 @@ package project import ( - "reflect" "testing" "github.com/azure/azure-dev/cli/azd/internal/scaffold" @@ -75,35 +74,3 @@ func Test_genBicepParamsFromEnvSubst(t *testing.T) { }) } } - -func TestServiceConfigEnvToScaffoldEnv(t *testing.T) { - tests := []struct { - name string - serviceConfig *ServiceConfig - wantScaffoldEnv []scaffold.Env - }{ - { - name: "eureka-and-config-server", - serviceConfig: &ServiceConfig{ - Env: map[string]string{ - "eureka.client.serviceUrl.defaultZone": "${DISCOVERY-SERVER_BASE_URL}/eureka", - "eureka.instance.prefer-ip-address": "true", - "spring.config.import": "optional:configserver:${CONFIG-SERVER_BASE_URL}", - }, - }, - wantScaffoldEnv: []scaffold.Env{ - {Name: "eureka.client.serviceUrl.defaultZone", Value: "\\${DISCOVERY-SERVER_BASE_URL}/eureka"}, - {Name: "eureka.instance.prefer-ip-address", Value: "true"}, - {Name: "spring.config.import", Value: "optional:configserver:\\${CONFIG-SERVER_BASE_URL}"}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - actual := serviceConfigEnv(tt.serviceConfig) - if !reflect.DeepEqual(actual, tt.wantScaffoldEnv) { - t.Errorf("serviceConfigEnv() got = %v, want %v", actual, tt.wantScaffoldEnv) - } - }) - } -} From d91b456f0f389ec04ddd71b403572b27d67c64ec Mon Sep 17 00:00:00 2001 From: haozhang Date: Mon, 2 Dec 2024 15:45:26 +0800 Subject: [PATCH 19/19] use func to refactor --- cli/azd/internal/repository/app_init.go | 45 ++++++++++--------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/cli/azd/internal/repository/app_init.go b/cli/azd/internal/repository/app_init.go index 841346ef0fa..d4308aeed3c 100644 --- a/cli/azd/internal/repository/app_init.go +++ b/cli/azd/internal/repository/app_init.go @@ -558,12 +558,20 @@ func (i *Initializer) prjConfigFromDetect( for _, dep := range prj.Dependencies { switch dep { case appdetect.JavaEurekaClient: - err := appendJavaEurekaClientEnv(&svc, javaEurekaServerService, spec) + err := appendJavaEurekaOrConfigClientEnv( + &svc, + javaEurekaServerService, + project.ResourceTypeJavaEurekaServer, + spec) if err != nil { return config, err } case appdetect.JavaConfigClient: - err := appendJavaConfigClientEnv(&svc, javaConfigServerService, spec) + err := appendJavaEurekaOrConfigClientEnv( + &svc, + javaConfigServerService, + project.ResourceTypeJavaConfigServer, + spec) if err != nil { return config, err } @@ -953,42 +961,23 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string } } -func appendJavaEurekaClientEnv(svc *project.ServiceConfig, - javaEurekaServerService project.ServiceConfig, - infraSpec *scaffold.InfraSpec) error { - if svc.Env == nil { - svc.Env = map[string]string{} - } - eurekaClientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ - Name: javaEurekaServerService.Name, - Type: project.ResourceTypeJavaEurekaServer, - }, infraSpec) - if err != nil { - return err - } - - for _, env := range eurekaClientEnvs { - svc.Env[env.Name] = env.Value - } - return nil -} - -func appendJavaConfigClientEnv(svc *project.ServiceConfig, - javaConfigServerService project.ServiceConfig, +func appendJavaEurekaOrConfigClientEnv(svc *project.ServiceConfig, + javaEurekaOrConfigServerService project.ServiceConfig, + resourceType project.ResourceType, infraSpec *scaffold.InfraSpec) error { if svc.Env == nil { svc.Env = map[string]string{} } - configClientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ - Name: javaConfigServerService.Name, - Type: project.ResourceTypeJavaConfigServer, + clientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{ + Name: javaEurekaOrConfigServerService.Name, + Type: resourceType, }, infraSpec) if err != nil { return err } - for _, env := range configClientEnvs { + for _, env := range clientEnvs { svc.Env[env.Name] = env.Value } return nil