diff --git a/cli/azd/pkg/project/importer.go b/cli/azd/pkg/project/importer.go index ac79ec76f2c..f684ae7881c 100644 --- a/cli/azd/pkg/project/importer.go +++ b/cli/azd/pkg/project/importer.go @@ -55,6 +55,9 @@ func (im *ImportManager) ServiceStable(ctx context.Context, projectConfig *Proje allServices := make(map[string]*ServiceConfig) for name, svcConfig := range projectConfig.Services { + if svcConfig == nil { + continue + } if svcConfig.Language == ServiceLanguageDotNet { if canImport, err := im.dotNetImporter.CanImport(ctx, svcConfig.Path()); canImport { if len(projectConfig.Services) != 1 { @@ -267,6 +270,9 @@ func (im *ImportManager) validateServiceDependencies(services []*ServiceConfig, // HasAppHost returns true when there is one AppHost (Aspire) in the project. func (im *ImportManager) HasAppHost(ctx context.Context, projectConfig *ProjectConfig) bool { for _, svcConfig := range projectConfig.Services { + if svcConfig == nil { + continue + } if svcConfig.Language == ServiceLanguageDotNet { if canImport, err := im.dotNetImporter.CanImport(ctx, svcConfig.Path()); canImport { return true @@ -329,6 +335,9 @@ func (im *ImportManager) ProjectInfrastructure(ctx context.Context, projectConfi // Temp infra from AppHost for _, svcConfig := range projectConfig.Services { + if svcConfig == nil { + continue + } if svcConfig.Language == ServiceLanguageDotNet { if canImport, err := im.dotNetImporter.CanImport(ctx, svcConfig.Path()); canImport { if len(projectConfig.Services) != 1 { @@ -425,6 +434,9 @@ func pathHasModule(path, module string) (bool, error) { // rooted at the project directory. func (im *ImportManager) GenerateAllInfrastructure(ctx context.Context, projectConfig *ProjectConfig) (fs.FS, error) { for _, svcConfig := range projectConfig.Services { + if svcConfig == nil { + continue + } if svcConfig.Language == ServiceLanguageDotNet { if canImport, err := im.dotNetImporter.CanImport(ctx, svcConfig.Path()); canImport { if len(projectConfig.Services) != 1 { diff --git a/cli/azd/pkg/project/project.go b/cli/azd/pkg/project/project.go index a4b94cc3e8a..490e341d23f 100644 --- a/cli/azd/pkg/project/project.go +++ b/cli/azd/pkg/project/project.go @@ -92,6 +92,12 @@ func Parse(ctx context.Context, yamlContent string) (*ProjectConfig, error) { projectConfig.Infra.Path = filepath.FromSlash(projectConfig.Infra.Path) for key, svc := range projectConfig.Services { + if svc == nil { + return nil, fmt.Errorf( + "service '%s' has empty configuration, must specify at least 'host' and either 'language' or 'image'", + key, + ) + } svc.Name = key svc.Project = &projectConfig svc.EventDispatcher = ext.NewEventDispatcher[ServiceLifecycleEventArgs]() diff --git a/cli/azd/pkg/project/project_config_test.go b/cli/azd/pkg/project/project_config_test.go index ca6688abf9d..4615befeaa9 100644 --- a/cli/azd/pkg/project/project_config_test.go +++ b/cli/azd/pkg/project/project_config_test.go @@ -55,6 +55,10 @@ func TestProjectConfigParse_Invalid(t *testing.T) { azd: notarange `), }, + { + name: "EmptyServiceDefinition", + projectConfig: "name: test-empty-service\nservices:\n web:\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -65,6 +69,15 @@ func TestProjectConfigParse_Invalid(t *testing.T) { } } +func TestProjectConfigParse_EmptyServiceReturnsError(t *testing.T) { + yaml := "name: test-empty-service\nservices:\n web:\n" + ctx := t.Context() + _, err := Parse(ctx, yaml) + require.Error(t, err) + require.Contains(t, err.Error(), "web") + require.Contains(t, err.Error(), "empty configuration") +} + func TestProjectConfigDefaults(t *testing.T) { const testProj = ` name: test-proj