From 103a005ae9c7df4edebc7a06dcd137b35107f5d3 Mon Sep 17 00:00:00 2001 From: Xiaolu Dai Date: Thu, 26 Sep 2024 16:59:29 +0800 Subject: [PATCH] refactor the java analyzer --- cli/azd/internal/appdetect/appdetect.go | 18 ++--- .../{java_project.go => azure_yaml.go} | 33 +++++++- .../appdetect/javaanalyze/java_analyzer.go | 41 ---------- .../javaanalyze/project_analyzer_java.go | 38 +++++++++ ..._analyzer.go => project_analyzer_maven.go} | 55 ++++++++----- .../javaanalyze/project_analyzer_spring.go | 78 +++++++++++++++++++ .../appdetect/javaanalyze/rule_engine.go | 14 ++-- .../appdetect/javaanalyze/rule_mongo.go | 12 +-- .../appdetect/javaanalyze/rule_mysql.go | 12 +-- .../appdetect/javaanalyze/rule_redis.go | 7 +- .../appdetect/javaanalyze/rule_service.go | 14 ++-- .../appdetect/javaanalyze/rule_servicebus.go | 30 ------- .../javaanalyze/rule_servicebus_scsb.go | 62 +++++++++++++++ .../appdetect/javaanalyze/rule_storage.go | 12 +-- cli/azd/internal/repository/infra_confirm.go | 70 +++++++++++++++++ cli/azd/internal/repository/infra_prompt.go | 38 +++++++++ cli/azd/internal/scaffold/spec.go | 12 +++ 17 files changed, 409 insertions(+), 137 deletions(-) rename cli/azd/internal/appdetect/javaanalyze/{java_project.go => azure_yaml.go} (69%) delete mode 100644 cli/azd/internal/appdetect/javaanalyze/java_analyzer.go create mode 100644 cli/azd/internal/appdetect/javaanalyze/project_analyzer_java.go rename cli/azd/internal/appdetect/javaanalyze/{pom_analyzer.go => project_analyzer_maven.go} (51%) create mode 100644 cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go delete mode 100644 cli/azd/internal/appdetect/javaanalyze/rule_servicebus.go create mode 100644 cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go create mode 100644 cli/azd/internal/repository/infra_prompt.go diff --git a/cli/azd/internal/appdetect/appdetect.go b/cli/azd/internal/appdetect/appdetect.go index 665b71c1da3..122ba242fa6 100644 --- a/cli/azd/internal/appdetect/appdetect.go +++ b/cli/azd/internal/appdetect/appdetect.go @@ -354,22 +354,22 @@ func analyze(projects []Project) []Project { return result } -func enrichFromJavaProject(javaProject javaanalyze.JavaProject, project *Project) { +func enrichFromJavaProject(azureYaml javaanalyze.AzureYaml, project *Project) { // if there is only one project, we can safely assume that it is the main project - for _, resource := range javaProject.Resources { - if resource.Type == "Azure Storage" { + for _, resource := range azureYaml.Resources { + if resource.GetType() == "Azure Storage" { // project.DatabaseDeps = append(project.DatabaseDeps, Db) - } else if resource.Type == "MySQL" { + } else if resource.GetType() == "MySQL" { project.DatabaseDeps = append(project.DatabaseDeps, DbMySql) - } else if resource.Type == "PostgreSQL" { + } else if resource.GetType() == "PostgreSQL" { project.DatabaseDeps = append(project.DatabaseDeps, DbPostgres) - } else if resource.Type == "SQL Server" { + } else if resource.GetType() == "SQL Server" { project.DatabaseDeps = append(project.DatabaseDeps, DbSqlServer) - } else if resource.Type == "Redis" { + } else if resource.GetType() == "Redis" { project.DatabaseDeps = append(project.DatabaseDeps, DbRedis) - } else if resource.Type == "Azure Service Bus" { + } else if resource.GetType() == "Azure Service Bus" { project.AzureDeps = append(project.AzureDeps, AzureServiceBus) - } else if resource.Type == "Azure Storage" { + } else if resource.GetType() == "Azure Storage" { project.AzureDeps = append(project.AzureDeps, AzureStorage) } } diff --git a/cli/azd/internal/appdetect/javaanalyze/java_project.go b/cli/azd/internal/appdetect/javaanalyze/azure_yaml.go similarity index 69% rename from cli/azd/internal/appdetect/javaanalyze/java_project.go rename to cli/azd/internal/appdetect/javaanalyze/azure_yaml.go index 9b494d24426..41e848c88cd 100644 --- a/cli/azd/internal/appdetect/javaanalyze/java_project.go +++ b/cli/azd/internal/appdetect/javaanalyze/azure_yaml.go @@ -1,11 +1,18 @@ package javaanalyze -type JavaProject struct { +type AzureYaml struct { Service *Service `json:"service"` - Resources []Resource `json:"resources"` + Resources []IResource `json:"resources"` ServiceBindings []ServiceBinding `json:"serviceBindings"` } +type IResource interface { + GetName() string + GetType() string + GetBicepParameters() []BicepParameter + GetBicepProperties() []BicepProperty +} + type Resource struct { Name string `json:"name"` Type string `json:"type"` @@ -13,6 +20,28 @@ type Resource struct { BicepProperties []BicepProperty `json:"bicepProperties"` } +func (r *Resource) GetName() string { + return r.Name +} + +func (r *Resource) GetType() string { + return r.Type +} + +func (r *Resource) GetBicepParameters() []BicepParameter { + return r.BicepParameters +} + +func (r *Resource) GetBicepProperties() []BicepProperty { + return r.BicepProperties +} + +type ServiceBusResource struct { + Resource + Queues []string `json:"queues"` + TopicAndSubscriptions []string `json:"topicAndSubscriptions"` +} + type BicepParameter struct { Name string `json:"name"` Description string `json:"description"` diff --git a/cli/azd/internal/appdetect/javaanalyze/java_analyzer.go b/cli/azd/internal/appdetect/javaanalyze/java_analyzer.go deleted file mode 100644 index cd7bf9bec00..00000000000 --- a/cli/azd/internal/appdetect/javaanalyze/java_analyzer.go +++ /dev/null @@ -1,41 +0,0 @@ -package javaanalyze - -import ( - "os" -) - -func Analyze(path string) []JavaProject { - result := []JavaProject{} - rules := []rule{ - &ruleService{}, - &ruleMysql{}, - &ruleStorage{}, - &ruleServiceBus{}, - } - - entries, err := os.ReadDir(path) - if err == nil { - for _, entry := range entries { - if "pom.xml" == entry.Name() { - mavenProject, _ := ParsePOM(path + "/" + entry.Name()) - - // if it has submodules - if len(mavenProject.Modules) > 0 { - for _, m := range mavenProject.Modules { - // analyze the submodules - subModule, _ := ParsePOM(path + "/" + m + "/pom.xml") - javaProject, _ := ApplyRules(subModule, rules) - result = append(result, *javaProject) - } - } else { - // analyze the maven project - javaProject, _ := ApplyRules(mavenProject, rules) - result = append(result, *javaProject) - } - } - //fmt.Printf("\tentry: %s", entry.Name()) - } - } - - return result -} diff --git a/cli/azd/internal/appdetect/javaanalyze/project_analyzer_java.go b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_java.go new file mode 100644 index 00000000000..bdb0c9cf38a --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_java.go @@ -0,0 +1,38 @@ +package javaanalyze + +import "os" + +type javaProject struct { + springProject springProject + mavenProject mavenProject +} + +func Analyze(path string) []AzureYaml { + var result []AzureYaml + rules := []rule{ + &ruleService{}, + &ruleMysql{}, + &ruleStorage{}, + &ruleServiceBusScsb{}, + } + + entries, err := os.ReadDir(path) + if err == nil { + for _, entry := range entries { + if "pom.xml" == entry.Name() { + mavenProjects, _ := analyzeMavenProject(path) + + for _, mavenProject := range mavenProjects { + javaProject := &javaProject{ + mavenProject: mavenProject, + springProject: analyzeSpringProject(mavenProject.path), + } + azureYaml, _ := applyRules(javaProject, rules) + result = append(result, *azureYaml) + } + } + } + } + + return result +} diff --git a/cli/azd/internal/appdetect/javaanalyze/pom_analyzer.go b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_maven.go similarity index 51% rename from cli/azd/internal/appdetect/javaanalyze/pom_analyzer.go rename to cli/azd/internal/appdetect/javaanalyze/project_analyzer_maven.go index 0c7ad862049..6f79d0f73bd 100644 --- a/cli/azd/internal/appdetect/javaanalyze/pom_analyzer.go +++ b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_maven.go @@ -5,28 +5,30 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" ) -// MavenProject represents the top-level structure of a Maven POM file. -type MavenProject struct { - XMLName xml.Name `xml:"project"` - Parent Parent `xml:"parent"` +// mavenProject represents the top-level structure of a Maven POM file. +type mavenProject struct { + XmlName xml.Name `xml:"project"` + Parent parent `xml:"parent"` Modules []string `xml:"modules>module"` // Capture the modules - Dependencies []Dependency `xml:"dependencies>dependency"` - DependencyManagement DependencyManagement `xml:"dependencyManagement"` - Build Build `xml:"build"` - Path string + Dependencies []dependency `xml:"dependencies>dependency"` + DependencyManagement dependencyManagement `xml:"dependencyManagement"` + Build build `xml:"build"` + path string + spring springProject } // Parent represents the parent POM if this project is a module. -type Parent struct { +type parent struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` } // Dependency represents a single Maven dependency. -type Dependency struct { +type dependency struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` @@ -34,25 +36,40 @@ type Dependency struct { } // DependencyManagement includes a list of dependencies that are managed. -type DependencyManagement struct { - Dependencies []Dependency `xml:"dependencies>dependency"` +type dependencyManagement struct { + Dependencies []dependency `xml:"dependencies>dependency"` } // Build represents the build configuration which can contain plugins. -type Build struct { - Plugins []Plugin `xml:"plugins>plugin"` +type build struct { + Plugins []plugin `xml:"plugins>plugin"` } // Plugin represents a build plugin. -type Plugin struct { +type plugin struct { GroupId string `xml:"groupId"` ArtifactId string `xml:"artifactId"` Version string `xml:"version"` //Configuration xml.Node `xml:"configuration"` } -// ParsePOM Parse the POM file. -func ParsePOM(filePath string) (*MavenProject, error) { +func analyzeMavenProject(projectPath string) ([]mavenProject, error) { + rootProject, _ := analyze(projectPath + "/pom.xml") + var result []mavenProject + + // if it has submodules + if len(rootProject.Modules) > 0 { + for _, m := range rootProject.Modules { + subModule, _ := analyze(projectPath + "/" + m + "/pom.xml") + result = append(result, *subModule) + } + } else { + result = append(result, *rootProject) + } + return result, nil +} + +func analyze(filePath string) (*mavenProject, error) { xmlFile, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("error opening file: %w", err) @@ -64,12 +81,12 @@ func ParsePOM(filePath string) (*MavenProject, error) { return nil, fmt.Errorf("error reading file: %w", err) } - var project MavenProject + var project mavenProject if err := xml.Unmarshal(bytes, &project); err != nil { return nil, fmt.Errorf("error parsing XML: %w", err) } - project.Path = filePath + project.path = filepath.Dir(filePath) return &project, nil } diff --git a/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go new file mode 100644 index 00000000000..85047325da4 --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/project_analyzer_spring.go @@ -0,0 +1,78 @@ +package javaanalyze + +import ( + "fmt" + "gopkg.in/yaml.v3" + "io/ioutil" + "log" +) + +type springProject struct { + applicationProperties map[string]interface{} +} + +func analyzeSpringProject(projectPath string) springProject { + return springProject{ + applicationProperties: findSpringApplicationProperties(projectPath), + } +} + +func findSpringApplicationProperties(projectPath string) map[string]interface{} { + yamlFilePath := projectPath + "/src/main/resources/application.yml" + data, err := ioutil.ReadFile(yamlFilePath) + if err != nil { + log.Fatalf("error reading YAML file: %v", err) + } + + // Parse the YAML into a yaml.Node + var root yaml.Node + err = yaml.Unmarshal(data, &root) + if err != nil { + log.Fatalf("error unmarshalling YAML: %v", err) + } + + result := make(map[string]interface{}) + parseYAML("", &root, result) + + return result +} + +// Recursively parse the YAML and build dot-separated keys into a map +func parseYAML(prefix string, node *yaml.Node, result map[string]interface{}) { + switch node.Kind { + case yaml.DocumentNode: + // Process each document's content + for _, contentNode := range node.Content { + parseYAML(prefix, contentNode, result) + } + case yaml.MappingNode: + // Process key-value pairs in a map + for i := 0; i < len(node.Content); i += 2 { + keyNode := node.Content[i] + valueNode := node.Content[i+1] + + // Ensure the key is a scalar + if keyNode.Kind != yaml.ScalarNode { + continue + } + + keyStr := keyNode.Value + newPrefix := keyStr + if prefix != "" { + newPrefix = prefix + "." + keyStr + } + parseYAML(newPrefix, valueNode, result) + } + case yaml.SequenceNode: + // Process items in a sequence (list) + for i, item := range node.Content { + newPrefix := fmt.Sprintf("%s[%d]", prefix, i) + parseYAML(newPrefix, item, result) + } + case yaml.ScalarNode: + // If it's a scalar value, add it to the result map + result[prefix] = node.Value + default: + // Handle other node types if necessary + } +} diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_engine.go b/cli/azd/internal/appdetect/javaanalyze/rule_engine.go index 173cc88096b..630d2d0ebf4 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_engine.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_engine.go @@ -1,17 +1,17 @@ package javaanalyze type rule interface { - Match(*MavenProject) bool - Apply(*JavaProject) + match(project *javaProject) bool + apply(azureYaml *AzureYaml) } -func ApplyRules(mavenProject *MavenProject, rules []rule) (*JavaProject, error) { - javaProject := &JavaProject{} +func applyRules(javaProject *javaProject, rules []rule) (*AzureYaml, error) { + azureYaml := &AzureYaml{} for _, r := range rules { - if r.Match(mavenProject) { - r.Apply(javaProject) + if r.match(javaProject) { + r.apply(azureYaml) } } - return javaProject, nil + return azureYaml, nil } diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_mongo.go b/cli/azd/internal/appdetect/javaanalyze/rule_mongo.go index 78ee0999c23..5ca181970a6 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_mongo.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_mongo.go @@ -3,9 +3,9 @@ package javaanalyze type ruleMongo struct { } -func (mr *ruleMongo) Match(mavenProject *MavenProject) bool { - if mavenProject.Dependencies != nil { - for _, dep := range mavenProject.Dependencies { +func (mr *ruleMongo) match(javaProject *javaProject) bool { + if javaProject.mavenProject.Dependencies != nil { + for _, dep := range javaProject.mavenProject.Dependencies { if dep.GroupId == "org.springframework.boot" && dep.ArtifactId == "spring-boot-starter-data-mongodb" { return true } @@ -14,13 +14,13 @@ func (mr *ruleMongo) Match(mavenProject *MavenProject) bool { return false } -func (mr *ruleMongo) Apply(javaProject *JavaProject) { - javaProject.Resources = append(javaProject.Resources, Resource{ +func (mr *ruleMongo) apply(azureYaml *AzureYaml) { + azureYaml.Resources = append(azureYaml.Resources, &Resource{ Name: "MongoDB", Type: "MongoDB", }) - javaProject.ServiceBindings = append(javaProject.ServiceBindings, ServiceBinding{ + azureYaml.ServiceBindings = append(azureYaml.ServiceBindings, ServiceBinding{ Name: "MongoDB", AuthType: AuthType_SYSTEM_MANAGED_IDENTITY, }) diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_mysql.go b/cli/azd/internal/appdetect/javaanalyze/rule_mysql.go index 1029eea1078..c98d317b101 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_mysql.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_mysql.go @@ -3,9 +3,9 @@ package javaanalyze type ruleMysql struct { } -func (mr *ruleMysql) Match(mavenProject *MavenProject) bool { - if mavenProject.Dependencies != nil { - for _, dep := range mavenProject.Dependencies { +func (mr *ruleMysql) match(javaProject *javaProject) bool { + if javaProject.mavenProject.Dependencies != nil { + for _, dep := range javaProject.mavenProject.Dependencies { if dep.GroupId == "com.mysql" && dep.ArtifactId == "mysql-connector-j" { return true } @@ -14,13 +14,13 @@ func (mr *ruleMysql) Match(mavenProject *MavenProject) bool { return false } -func (mr *ruleMysql) Apply(javaProject *JavaProject) { - javaProject.Resources = append(javaProject.Resources, Resource{ +func (mr *ruleMysql) apply(azureYaml *AzureYaml) { + azureYaml.Resources = append(azureYaml.Resources, &Resource{ Name: "MySQL", Type: "MySQL", }) - javaProject.ServiceBindings = append(javaProject.ServiceBindings, ServiceBinding{ + azureYaml.ServiceBindings = append(azureYaml.ServiceBindings, ServiceBinding{ Name: "MySQL", AuthType: AuthType_SYSTEM_MANAGED_IDENTITY, }) diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_redis.go b/cli/azd/internal/appdetect/javaanalyze/rule_redis.go index 1f5d437867b..59ef290ac9b 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_redis.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_redis.go @@ -3,13 +3,12 @@ package javaanalyze type ruleRedis struct { } -func (mr *ruleRedis) Match(mavenProject *MavenProject) bool { - +func (r *ruleRedis) match(javaProject *javaProject) bool { return false } -func (mr *ruleRedis) Apply(javaProject *JavaProject) { - javaProject.Resources = append(javaProject.Resources, Resource{ +func (r *ruleRedis) apply(azureYaml *AzureYaml) { + azureYaml.Resources = append(azureYaml.Resources, &Resource{ Name: "Redis", Type: "Redis", }) diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_service.go b/cli/azd/internal/appdetect/javaanalyze/rule_service.go index 8e6106d703a..8203848830f 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_service.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_service.go @@ -1,17 +1,17 @@ package javaanalyze type ruleService struct { - MavenProject *MavenProject + javaProject *javaProject } -func (mr *ruleService) Match(mavenProject *MavenProject) bool { - mr.MavenProject = mavenProject +func (r *ruleService) match(javaProject *javaProject) bool { + r.javaProject = javaProject return true } -func (mr *ruleService) Apply(javaProject *JavaProject) { - if javaProject.Service == nil { - javaProject.Service = &Service{} +func (r *ruleService) apply(azureYaml *AzureYaml) { + if azureYaml.Service == nil { + azureYaml.Service = &Service{} } - javaProject.Service.Path = mr.MavenProject.Path + azureYaml.Service.Path = r.javaProject.mavenProject.path } diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_servicebus.go b/cli/azd/internal/appdetect/javaanalyze/rule_servicebus.go deleted file mode 100644 index ef4e52e4129..00000000000 --- a/cli/azd/internal/appdetect/javaanalyze/rule_servicebus.go +++ /dev/null @@ -1,30 +0,0 @@ -package javaanalyze - -type ruleServiceBus struct { -} - -func (mr *ruleServiceBus) Match(mavenProject *MavenProject) bool { - if mavenProject.Dependencies != nil { - for _, dep := range mavenProject.Dependencies { - if dep.GroupId == "com.azure" && dep.ArtifactId == "" { - return true - } - if dep.GroupId == "com.azure.spring" && dep.ArtifactId == "spring-cloud-azure-stream-binder-servicebus" { - return true - } - } - } - return false -} - -func (mr *ruleServiceBus) Apply(javaProject *JavaProject) { - javaProject.Resources = append(javaProject.Resources, Resource{ - Name: "Azure Service Bus", - Type: "Azure Service Bus", - }) - - javaProject.ServiceBindings = append(javaProject.ServiceBindings, ServiceBinding{ - Name: "Azure Service Bus", - AuthType: AuthType_SYSTEM_MANAGED_IDENTITY, - }) -} diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go b/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go new file mode 100644 index 00000000000..4276527b56d --- /dev/null +++ b/cli/azd/internal/appdetect/javaanalyze/rule_servicebus_scsb.go @@ -0,0 +1,62 @@ +package javaanalyze + +import ( + "fmt" + "strings" +) + +type ruleServiceBusScsb struct { + javaProject *javaProject +} + +func (r *ruleServiceBusScsb) match(javaProject *javaProject) bool { + if javaProject.mavenProject.Dependencies != nil { + for _, dep := range javaProject.mavenProject.Dependencies { + if dep.GroupId == "com.azure.spring" && dep.ArtifactId == "spring-cloud-azure-stream-binder-servicebus" { + r.javaProject = javaProject + return true + } + } + } + return false +} + +// Function to find all properties that match the pattern `spring.cloud.stream.bindings..destination` +func findBindingDestinations(properties map[string]interface{}) map[string]string { + result := make(map[string]string) + + // Iterate through the properties map and look for matching keys + for key, value := range properties { + // Check if the key matches the pattern `spring.cloud.stream.bindings..destination` + if strings.HasPrefix(key, "spring.cloud.stream.bindings.") && strings.HasSuffix(key, ".destination") { + // Extract the binding name + bindingName := key[len("spring.cloud.stream.bindings.") : len(key)-len(".destination")] + // Store the binding name and destination value + result[bindingName] = fmt.Sprintf("%v", value) + } + } + + return result +} + +func (r *ruleServiceBusScsb) apply(azureYaml *AzureYaml) { + bindingDestinations := findBindingDestinations(r.javaProject.springProject.applicationProperties) + destinations := make([]string, 0, len(bindingDestinations)) + for bindingName, destination := range bindingDestinations { + destinations = append(destinations, destination) + fmt.Printf("Service Bus queue [%s] found for binding [%s]", destination, bindingName) + } + resource := ServiceBusResource{ + Resource: Resource{ + Name: "Azure Service Bus", + Type: "Azure Service Bus", + }, + Queues: destinations, + } + azureYaml.Resources = append(azureYaml.Resources, &resource) + + azureYaml.ServiceBindings = append(azureYaml.ServiceBindings, ServiceBinding{ + Name: "Azure Service Bus", + AuthType: AuthType_SYSTEM_MANAGED_IDENTITY, + }) +} diff --git a/cli/azd/internal/appdetect/javaanalyze/rule_storage.go b/cli/azd/internal/appdetect/javaanalyze/rule_storage.go index 5ec5dd0999b..557733ebb7b 100644 --- a/cli/azd/internal/appdetect/javaanalyze/rule_storage.go +++ b/cli/azd/internal/appdetect/javaanalyze/rule_storage.go @@ -3,9 +3,9 @@ package javaanalyze type ruleStorage struct { } -func (mr *ruleStorage) Match(mavenProject *MavenProject) bool { - if mavenProject.Dependencies != nil { - for _, dep := range mavenProject.Dependencies { +func (r *ruleStorage) match(javaProject *javaProject) bool { + if javaProject.mavenProject.Dependencies != nil { + for _, dep := range javaProject.mavenProject.Dependencies { if dep.GroupId == "com.azure" && dep.ArtifactId == "" { return true } @@ -26,13 +26,13 @@ func (mr *ruleStorage) Match(mavenProject *MavenProject) bool { return false } -func (mr *ruleStorage) Apply(javaProject *JavaProject) { - javaProject.Resources = append(javaProject.Resources, Resource{ +func (r *ruleStorage) apply(azureYaml *AzureYaml) { + azureYaml.Resources = append(azureYaml.Resources, &Resource{ Name: "Azure Storage", Type: "Azure Storage", }) - javaProject.ServiceBindings = append(javaProject.ServiceBindings, ServiceBinding{ + azureYaml.ServiceBindings = append(azureYaml.ServiceBindings, ServiceBinding{ Name: "Azure Storage", AuthType: AuthType_SYSTEM_MANAGED_IDENTITY, }) diff --git a/cli/azd/internal/repository/infra_confirm.go b/cli/azd/internal/repository/infra_confirm.go index eead3d61f7f..b577fdc6e9b 100644 --- a/cli/azd/internal/repository/infra_confirm.go +++ b/cli/azd/internal/repository/infra_confirm.go @@ -103,6 +103,13 @@ func (i *Initializer) infraSpecFromDetect( } } + for azureDep := range detect.AzureDeps { + infraSpec, err := i.promptForAzureResource(ctx, azureDep, spec) + if err != nil { + return infraSpec, err + } + } + for _, svc := range detect.Services { name := filepath.Base(svc.Path) serviceSpec := scaffold.ServiceSpec{ @@ -208,3 +215,66 @@ func (i *Initializer) infraSpecFromDetect( return spec, nil } + +func (i *Initializer) promptForAzureResource( + ctx context.Context, + azureDep appdetect.AzureDep, + spec scaffold.InfraSpec) (scaffold.InfraSpec, error) { +azureDepPrompt: + for { + azureDepName, err := i.console.Prompt(ctx, input.ConsoleOptions{ + Message: fmt.Sprintf("Input the name of the Azure dependency (%s)", azureDep.Display()), + Help: "Hint: Azure dependency name\n\n" + + "Name of the Azure dependency that the app connects to. " + + "This dependency will be created after running azd provision or azd up." + + "\nYou may be able to skip this step by hitting enter, in which case the dependency will not be created.", + }) + if err != nil { + return scaffold.InfraSpec{}, err + } + + if strings.ContainsAny(azureDepName, " ") { + i.console.MessageUxItem(ctx, &ux.WarningMessage{ + Description: "Dependency name contains whitespace. This might not be allowed by the Azure service.", + }) + confirm, err := i.console.Confirm(ctx, input.ConsoleOptions{ + Message: fmt.Sprintf("Continue with name '%s'?", azureDepName), + }) + if err != nil { + return scaffold.InfraSpec{}, err + } + + if !confirm { + continue azureDepPrompt + } + } else if !wellFormedDbNameRegex.MatchString(azureDepName) { + i.console.MessageUxItem(ctx, &ux.WarningMessage{ + Description: "Dependency name contains special characters. " + + "This might not be allowed by the Azure service.", + }) + confirm, err := i.console.Confirm(ctx, input.ConsoleOptions{ + Message: fmt.Sprintf("Continue with name '%s'?", azureDepName), + }) + if err != nil { + return scaffold.InfraSpec{}, err + } + + if !confirm { + continue azureDepPrompt + } + } + + switch azureDep { + case appdetect.AzureServiceBus: + + spec.AzureServiceBus = &scaffold.AzureDepServiceBus{ + Name: azureDepName, + } + break azureDepPrompt + case appdetect.AzureStorage: + break azureDepPrompt + } + break azureDepPrompt + } + return spec, nil +} diff --git a/cli/azd/internal/repository/infra_prompt.go b/cli/azd/internal/repository/infra_prompt.go new file mode 100644 index 00000000000..69622d40073 --- /dev/null +++ b/cli/azd/internal/repository/infra_prompt.go @@ -0,0 +1,38 @@ +package repository + +import ( + "github.com/azure/azure-dev/cli/azd/internal/appdetect" + "github.com/azure/azure-dev/cli/azd/internal/scaffold" +) + +type infraPrompt interface { + Type() string + Properties() map[string]string + Apply(spec *scaffold.InfraSpec) +} + +type serviceBusPrompt struct { + name string + queues []string + topicAndSubscriptions []string +} + +func (s *serviceBusPrompt) Type() string { + return appdetect.AzureServiceBus.Display() +} + +func (s *serviceBusPrompt) Properties() map[string]string { + return map[string]string{ + "name": "Service Bus namespace name", + "queues": "Comma-separated list of queue names", + "topicAndSubscriptions": "Comma-separated list of topic names and their subscriptions, of format 'topicName:subscription1,subscription2,...'", + } +} + +func (s *serviceBusPrompt) Apply(spec *scaffold.InfraSpec) { + if spec.AzureServiceBus == nil { + spec.AzureServiceBus = &scaffold.AzureDepServiceBus{} + } + spec.AzureServiceBus.Name = s.name + spec.AzureServiceBus.Queues = s.queues +} diff --git a/cli/azd/internal/scaffold/spec.go b/cli/azd/internal/scaffold/spec.go index 47d525619d4..feac1585671 100644 --- a/cli/azd/internal/scaffold/spec.go +++ b/cli/azd/internal/scaffold/spec.go @@ -13,6 +13,9 @@ type InfraSpec struct { DbPostgres *DatabasePostgres DbMySql *DatabaseMySql DbCosmosMongo *DatabaseCosmosMongo + + // Azure Service Bus + AzureServiceBus *AzureDepServiceBus } type Parameter struct { @@ -36,6 +39,12 @@ type DatabaseCosmosMongo struct { DatabaseName string } +type AzureDepServiceBus struct { + Name string + Queues []string + TopicsAndSubscriptions map[string][]string +} + type ServiceSpec struct { Name string Port int @@ -51,6 +60,9 @@ type ServiceSpec struct { DbMySql *DatabaseReference DbCosmosMongo *DatabaseReference DbRedis *DatabaseReference + + // Azure Service Bus + AzureServiceBus *AzureDepServiceBus } type Frontend struct {