From 9ea7ee6580528d98bfff017455de764343d1a5bd Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Thu, 26 Mar 2026 07:27:33 -0700 Subject: [PATCH 1/2] fix: return error instead of panic for empty service in azure.yaml When a service is defined in azure.yaml without any properties (e.g., `web:` with nothing below it), the YAML unmarshaller creates a nil *ServiceConfig entry. Previously this caused a nil pointer panic in Parse(). Now returns a clear error message. Fixes #7254 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/azd/pkg/project/importer.go | 12 ++++++++++++ cli/azd/pkg/project/project.go | 6 ++++++ cli/azd/pkg/project/project_config_test.go | 13 +++++++++++++ 3 files changed, 31 insertions(+) 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 From a467386c23897f1c4dd878e418d87d4bbe1c0783 Mon Sep 17 00:00:00 2001 From: Shayne Boyer Date: Thu, 26 Mar 2026 08:10:17 -0700 Subject: [PATCH 2/2] ci: retrigger after flaky Linux build failure