Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploy PetClinic microservices using azd #49

Merged
merged 22 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ words:
- postgre
- mysqladmin
- sjad
- configserver
languageSettings:
- languageId: go
ignoreRegExpList:
Expand Down
15 changes: 14 additions & 1 deletion cli/azd/internal/appdetect/appdetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const (
PyFlask Dependency = "flask"
PyDjango Dependency = "django"
PyFastApi Dependency = "fastapi"

JavaEurekaServer Dependency = "eureka-server"
JavaEurekaClient Dependency = "eureka-client"
JavaConfigServer Dependency = "config-server"
JavaConfigClient Dependency = "config-client"
)

var WebUIFrameworks = map[Dependency]struct{}{
Expand Down Expand Up @@ -89,6 +94,14 @@ func (f Dependency) Display() string {
return "Vite"
case JsNext:
return "Next.js"
case JavaEurekaServer:
return "JavaEurekaServer"
case JavaEurekaClient:
return "JavaEurekaClient"
case JavaConfigServer:
return "JavaConfigServer"
case JavaConfigClient:
return "JavaConfigClient"
}

return ""
Expand Down Expand Up @@ -312,7 +325,7 @@ 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 is an optional property of a project, and thus is different from other detectors
docker, err := detectDockerInDirectory(path, entries)
if err != nil {
return nil, fmt.Errorf("detecting docker project: %w", err)
Expand Down
6 changes: 3 additions & 3 deletions cli/azd/internal/appdetect/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import (
"context"
"encoding/xml"
"fmt"
"github.com/azure/azure-dev/cli/azd/internal/tracing"
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/azure/azure-dev/cli/azd/internal/tracing"
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
)

type javaDetector struct {
Expand Down Expand Up @@ -49,7 +50,6 @@ func (jd *javaDetector) DetectProject(ctx context.Context, path string, entries
}
}

_ = currentRoot // use currentRoot here in the analysis
result, err := detectDependencies(currentRoot, project, &Project{
Language: Java,
Path: path,
Expand Down
34 changes: 34 additions & 0 deletions cli/azd/internal/appdetect/spring_boot.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func detectAzureDependenciesByAnalyzingSpringBootProject(
detectEventHubs(azdProject, &springBootProject)
detectStorageAccount(azdProject, &springBootProject)
detectSpringCloudAzure(azdProject, &springBootProject)
detectSpringCloudEureka(azdProject, &springBootProject)
haoozhang marked this conversation as resolved.
Show resolved Hide resolved
detectSpringCloudConfig(azdProject, &springBootProject)
}

func detectDatabases(azdProject *Project, springBootProject *SpringBootProject) {
Expand Down Expand Up @@ -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, 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, JavaEurekaClient)
logServiceAddedAccordingToMavenDependency(JavaEurekaClient.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, 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, JavaConfigClient)
logServiceAddedAccordingToMavenDependency(JavaConfigClient.Display(), targetGroupId, targetArtifactId)
}
}

func logServiceAddedAccordingToMavenDependency(resourceName, groupId string, artifactId string) {
logServiceAddedAccordingToMavenDependencyAndExtraCondition(resourceName, groupId, artifactId, "")
}
Expand Down
3 changes: 2 additions & 1 deletion cli/azd/internal/appdetect/spring_boot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package appdetect

import (
"encoding/xml"
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

func TestDetectSpringBootVersion(t *testing.T) {
Expand Down
74 changes: 74 additions & 0 deletions cli/azd/internal/repository/app_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,26 @@ func (i *Initializer) prjConfigFromDetect(
Resources: map[string]*project.ResourceConfig{},
}

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{}
for _, prj := range detect.Services {
svc, err := ServiceFromDetect(root, "", prj)
Expand Down Expand Up @@ -535,6 +555,29 @@ func (i *Initializer) prjConfigFromDetect(
}
}

for _, dep := range prj.Dependencies {
switch dep {
case appdetect.JavaEurekaClient:
err := appendJavaEurekaOrConfigClientEnv(
&svc,
javaEurekaServerService,
project.ResourceTypeJavaEurekaServer,
spec)
if err != nil {
return config, err
}
case appdetect.JavaConfigClient:
err := appendJavaEurekaOrConfigClientEnv(
&svc,
javaConfigServerService,
project.ResourceTypeJavaConfigServer,
spec)
if err != nil {
return config, err
}
}
}

config.Services[svc.Name] = &svc
svcMapping[prj.Path] = svc.Name
}
Expand Down Expand Up @@ -697,6 +740,15 @@ func (i *Initializer) prjConfigFromDetect(
}
props.Port = port

for _, dep := range svc.Dependencies {
switch dep {
case appdetect.JavaEurekaClient:
resSpec.Uses = append(resSpec.Uses, javaEurekaServerService.Name)
case appdetect.JavaConfigClient:
resSpec.Uses = append(resSpec.Uses, javaConfigServerService.Name)
}
}

for _, db := range svc.DatabaseDeps {
// filter out databases that were removed
if _, ok := detect.Databases[db]; !ok {
Expand Down Expand Up @@ -908,3 +960,25 @@ func promptSpringBootVersion(console input.Console, ctx context.Context) (string
return appdetect.UnknownSpringBootVersion, nil
}
}

func appendJavaEurekaOrConfigClientEnv(svc *project.ServiceConfig,
javaEurekaOrConfigServerService project.ServiceConfig,
resourceType project.ResourceType,
infraSpec *scaffold.InfraSpec) error {
if svc.Env == nil {
svc.Env = map[string]string{}
}

clientEnvs, err := project.GetResourceConnectionEnvs(&project.ResourceConfig{
Name: javaEurekaOrConfigServerService.Name,
Type: resourceType,
}, infraSpec)
if err != nil {
return err
}

for _, env := range clientEnvs {
svc.Env[env.Name] = env.Value
}
return nil
}
8 changes: 8 additions & 0 deletions cli/azd/internal/repository/infra_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.JavaEurekaServer:
return 8761, nil
case appdetect.JavaConfigServer:
return 8888, nil
rujche marked this conversation as resolved.
Show resolved Hide resolved
}
}
return 8080, nil
}
return 80, nil
Expand Down
12 changes: 11 additions & 1 deletion cli/azd/internal/scaffold/bicep_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions cli/azd/pkg/project/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 ""
Expand Down
26 changes: 22 additions & 4 deletions cli/azd/pkg/project/scaffold_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ package project
import (
"context"
"fmt"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/input"

"github.com/azure/azure-dev/cli/azd/internal/scaffold"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
Expand Down Expand Up @@ -205,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)
Expand Down Expand Up @@ -346,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
}
Expand Down Expand Up @@ -395,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
}
Expand Down Expand Up @@ -569,3 +574,16 @@ func printHintsAboutUseHostContainerApp(userResourceName string, usedResourceNam
console.Message(ctx, fmt.Sprintf("Environment 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
if svcConfig != nil {
for key, val := range svcConfig.Env {
envs = append(envs, scaffold.Env{
Name: key,
Value: val,
})
}
}
return envs
}
28 changes: 27 additions & 1 deletion cli/azd/pkg/project/scaffold_gen_environment_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -556,6 +556,32 @@ 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",
},
{
Name: "eureka.client.serviceUrl.defaultZone",
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", scaffold.GetContainerAppHost(usedResource.Name)),
},
}, nil
default:
return []scaffold.Env{}, unsupportedResourceTypeError(resourceType)
}
Expand Down
Loading
Loading