Skip to content

Commit

Permalink
add azure event hubs
Browse files Browse the repository at this point in the history
  • Loading branch information
saragluna committed Dec 24, 2024
1 parent ef05268 commit 4d445ce
Show file tree
Hide file tree
Showing 20 changed files with 1,464 additions and 127 deletions.
28 changes: 28 additions & 0 deletions cli/azd/internal/appdetect/appdetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ type Project struct {
// Experimental: Database dependencies inferred through heuristics while scanning dependencies in the project.
DatabaseDeps []DatabaseDep

// Experimental: Azure dependencies inferred through heuristics while scanning dependencies in the project.
AzureDeps []AzureDep

// Experimental: Metadata inferred through heuristics while scanning the project.
Metadata Metadata

// The path to the project directory.
Path string

Expand All @@ -151,6 +157,28 @@ type Project struct {
Docker *Docker
}

//type AzureDep string

type AzureDep interface {
ResourceDisplay() string
}

type AzureDepEventHubs struct {
EventHubsNamePropertyMap map[string]string
UseKafka bool
SpringBootVersion string
}

func (a AzureDepEventHubs) ResourceDisplay() string {
return "Azure Event Hubs"
}

type Metadata struct {
ContainsDependencySpringCloudAzureStarter bool
}

const UnknownSpringBootVersion string = "unknownSpringBootVersion"

func (p *Project) HasWebUIFramework() bool {
for _, f := range p.Dependencies {
if f.IsWebUIFramework() {
Expand Down
29 changes: 29 additions & 0 deletions cli/azd/internal/auth_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package internal

// AuthType defines different authentication types.
type AuthType string

const (
AuthTypeUnspecified AuthType = "unspecified"
// Username and password, or key based authentication
AuthTypePassword AuthType = "password"
// Connection string authentication
AuthTypeConnectionString AuthType = "connectionString"
// Microsoft EntraID token credential
AuthTypeUserAssignedManagedIdentity AuthType = "userAssignedManagedIdentity"
)

func GetAuthTypeDescription(authType AuthType) string {
switch authType {
case AuthTypeUnspecified:
return "Unspecified"
case AuthTypePassword:
return "Username and password"
case AuthTypeConnectionString:
return "Connection string"
case AuthTypeUserAssignedManagedIdentity:
return "User assigned managed identity"
default:
return "Unspecified"
}
}
180 changes: 179 additions & 1 deletion cli/azd/internal/repository/app_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ var dbMap = map[appdetect.DatabaseDep]struct{}{

var featureCompose = alpha.MustFeatureKey("compose")

var azureDepMap = map[string]struct{}{
appdetect.AzureDepEventHubs{}.ResourceDisplay(): {},
}

// InitFromApp initializes the infra directory and project file from the current existing app.
func (i *Initializer) InitFromApp(
ctx context.Context,
Expand Down Expand Up @@ -120,10 +124,44 @@ func (i *Initializer) InitFromApp(
i.console.StopSpinner(ctx, title, input.StepDone)

var prjAppHost []appdetect.Project
for _, prj := range projects {
for index, prj := range projects {
if prj.Language == appdetect.DotNetAppHost {
prjAppHost = append(prjAppHost, prj)
}

if prj.Language == appdetect.Java {
var hasKafkaDep bool
for depIndex, dep := range prj.AzureDeps {
if eventHubs, ok := dep.(appdetect.AzureDepEventHubs); ok {
// prompt spring boot version if not detected for kafka
if eventHubs.UseKafka {
hasKafkaDep = true
springBootVersion := eventHubs.SpringBootVersion
if springBootVersion == appdetect.UnknownSpringBootVersion {
springBootVersionInput, err := promptSpringBootVersion(i.console, ctx)
if err != nil {
return err
}
eventHubs.SpringBootVersion = springBootVersionInput
prj.AzureDeps[depIndex] = eventHubs
}
}
// prompt event hubs name if not detected
for property, eventHubsName := range eventHubs.EventHubsNamePropertyMap {
if eventHubsName == "" {
promptMissingPropertyAndExit(i.console, ctx, property)
}
}
}
}

if hasKafkaDep && !prj.Metadata.ContainsDependencySpringCloudAzureStarter {
err := processSpringCloudAzureDepByPrompt(i.console, ctx, &projects[index])
if err != nil {
return err
}
}
}
}

if len(prjAppHost) > 1 {
Expand Down Expand Up @@ -455,6 +493,39 @@ func (i *Initializer) prjConfigFromDetect(
dbNames[database] = db.Name
}

for _, azureDepPair := range detect.AzureDeps {
azureDep := azureDepPair.first
authType, err := chooseAuthTypeByPrompt(
azureDep.ResourceDisplay(),
[]internal.AuthType{internal.AuthTypeUserAssignedManagedIdentity, internal.AuthTypeConnectionString},
ctx,
i.console)
if err != nil {
return config, err
}
switch azureDep := azureDep.(type) {
case appdetect.AzureDepEventHubs:
if azureDep.UseKafka {
config.Resources["kafka"] = &project.ResourceConfig{
Type: project.ResourceTypeMessagingKafka,
Props: project.KafkaProps{
Topics: distinctValues(azureDep.EventHubsNamePropertyMap),
AuthType: authType,
SpringBootVersion: azureDep.SpringBootVersion,
},
}
} else {
config.Resources["eventhubs"] = &project.ResourceConfig{
Type: project.ResourceTypeMessagingEventHubs,
Props: project.EventHubsProps{
EventHubNames: distinctValues(azureDep.EventHubsNamePropertyMap),
AuthType: authType,
},
}
}
}
}

backends := []*project.ResourceConfig{}
frontends := []*project.ResourceConfig{}

Expand Down Expand Up @@ -483,6 +554,17 @@ func (i *Initializer) prjConfigFromDetect(
resSpec.Uses = append(resSpec.Uses, dbNames[db])
}

for _, azureDep := range svc.AzureDeps {
switch azureDep.(type) {

Check failure on line 558 in cli/azd/internal/repository/app_init.go

View workflow job for this annotation

GitHub Actions / azd-lint (ubuntu-latest)

S1034: assigning the result of this type assertion to a variable (switch azureDep := azureDep.(type)) could eliminate type assertions in switch cases (gosimple)
case appdetect.AzureDepEventHubs:
if azureDep.(appdetect.AzureDepEventHubs).UseKafka {

Check failure on line 560 in cli/azd/internal/repository/app_init.go

View workflow job for this annotation

GitHub Actions / azd-lint (ubuntu-latest)

S1034(related information): could eliminate this type assertion (gosimple)
resSpec.Uses = append(resSpec.Uses, "kafka")
} else {
resSpec.Uses = append(resSpec.Uses, "eventhubs")
}
}
}

resSpec.Name = name
resSpec.Props = props
config.Resources[name] = &resSpec
Expand Down Expand Up @@ -578,3 +660,99 @@ func ServiceFromDetect(

return svc, nil
}

func chooseAuthTypeByPrompt(
name string,
authOptions []internal.AuthType,
ctx context.Context,
console input.Console) (internal.AuthType, error) {
var options []string
for _, option := range authOptions {
options = append(options, internal.GetAuthTypeDescription(option))
}
selection, err := console.Select(ctx, input.ConsoleOptions{
Message: "Choose auth type for " + name + ":",
Options: options,
})
if err != nil {
return internal.AuthTypeUnspecified, err
}
return authOptions[selection], nil
}

func promptMissingPropertyAndExit(console input.Console, ctx context.Context, key string) {
console.Message(ctx, fmt.Sprintf("No value was provided for %s. Please update the configuration file "+
"(like application.properties or application.yaml) with a valid value.", key))
os.Exit(0)
}

func distinctValues(input map[string]string) []string {
valueSet := make(map[string]struct{})
for _, value := range input {
valueSet[value] = struct{}{}
}

var result []string
for value := range valueSet {
result = append(result, value)
}

return result
}

func processSpringCloudAzureDepByPrompt(console input.Console, ctx context.Context, project *appdetect.Project) error {
continueOption, err := console.Select(ctx, input.ConsoleOptions{
Message: "Detected Kafka dependency but no spring-cloud-azure-starter found. Select an option",
Options: []string{
"Exit then I will manually add this dependency",
"Continue without this dependency, and provision Azure Event Hubs for Kafka",
"Continue without this dependency, and not provision Azure Event Hubs for Kafka",
},
})
if err != nil {
return err
}

switch continueOption {
case 0:
console.Message(ctx, "you have to manually add dependency com.azure.spring:spring-cloud-azure-starter. "+
"And use right version according to this page: "+
"https://github.com/Azure/azure-sdk-for-java/wiki/Spring-Versions-Mapping")
os.Exit(0)
case 1:
return nil
case 2:
// remove Kafka Azure Dep
var result []appdetect.AzureDep
for _, dep := range project.AzureDeps {
if eventHubs, ok := dep.(appdetect.AzureDepEventHubs); !(ok && eventHubs.UseKafka) {
result = append(result, dep)
}
}
project.AzureDeps = result
return nil
}
return nil
}

func promptSpringBootVersion(console input.Console, ctx context.Context) (string, error) {
selection, err := console.Select(ctx, input.ConsoleOptions{
Message: "No spring boot version detected, what is your spring boot version?",
Options: []string{
"Spring Boot 2.x",
"Spring Boot 3.x",
},
})
if err != nil {
return "", err
}

switch selection {
case 0:
return "2.x", nil
case 1:
return "3.x", nil
default:
return appdetect.UnknownSpringBootVersion, nil
}
}
12 changes: 12 additions & 0 deletions cli/azd/internal/repository/detect_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@ const (
EntryKindModified EntryKind = "modified"
)

type Pair struct {
first appdetect.AzureDep
second EntryKind
}

// detectConfirm handles prompting for confirming the detected services and databases
type detectConfirm struct {
// detected services and databases
Services []appdetect.Project
Databases map[appdetect.DatabaseDep]EntryKind
AzureDeps map[string]Pair

// the root directory of the project
root string
Expand All @@ -73,6 +79,12 @@ func (d *detectConfirm) Init(projects []appdetect.Project, root string) {
d.Databases[dbType] = EntryKindDetected
}
}

for _, azureDep := range project.AzureDeps {
if _, supported := azureDepMap[azureDep.ResourceDisplay()]; supported {
d.AzureDeps[azureDep.ResourceDisplay()] = Pair{azureDep, EntryKindDetected}
}
}
}

d.captureUsage(
Expand Down
23 changes: 14 additions & 9 deletions cli/azd/internal/repository/infra_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,24 @@ func (i *Initializer) infraSpecFromDetect(

switch db {
case appdetect.DbMongo:
serviceSpec.DbCosmosMongo = &scaffold.DatabaseReference{
DatabaseName: spec.DbCosmosMongo.DatabaseName,
}
err = scaffold.BindToMongoDb(&serviceSpec, spec.DbCosmosMongo)
case appdetect.DbPostgres:
serviceSpec.DbPostgres = &scaffold.DatabaseReference{
DatabaseName: spec.DbPostgres.DatabaseName,
}
err = scaffold.BindToPostgres(&serviceSpec, spec.DbPostgres)
case appdetect.DbRedis:
serviceSpec.DbRedis = &scaffold.DatabaseReference{
DatabaseName: "redis",
}
err = scaffold.BindToRedis(&serviceSpec, spec.DbRedis)
}
}

for _, azureDep := range svc.AzureDeps {
switch azureDep.(type) {
case appdetect.AzureDepEventHubs:
err = scaffold.BindToEventHubs(&serviceSpec, spec.AzureEventHubs)
}
}

if err != nil {
return scaffold.InfraSpec{}, err
}
spec.Services = append(spec.Services, serviceSpec)
}

Expand Down
4 changes: 3 additions & 1 deletion cli/azd/internal/repository/infra_confirm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
)

func TestInitializer_infraSpecFromDetect(t *testing.T) {
envs, _ := scaffold.GetServiceBindingEnvsForPostgres()
tests := []struct {
name string
detect detectConfirm
Expand Down Expand Up @@ -183,9 +184,10 @@ func TestInitializer_infraSpecFromDetect(t *testing.T) {
},
},
},
DbPostgres: &scaffold.DatabaseReference{
DbPostgres: &scaffold.DatabasePostgres{
DatabaseName: "myappdb",
},
Envs: envs,
},
{
Name: "js",
Expand Down
Loading

0 comments on commit 4d445ce

Please sign in to comment.