Skip to content

Commit

Permalink
Deploy PetClinic microservices using azd (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
haoozhang authored Dec 2, 2024
1 parent 85c5ce1 commit b5aceaa
Show file tree
Hide file tree
Showing 14 changed files with 215 additions and 13 deletions.
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)
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
}
}
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

0 comments on commit b5aceaa

Please sign in to comment.