From 140a37f253c14c1913083594aef17203b565ffd9 Mon Sep 17 00:00:00 2001 From: hemarina Date: Thu, 5 Mar 2026 15:44:07 -0800 Subject: [PATCH 1/6] update UI and add version check for failed ext --- cli/azd/cmd/middleware/extensions.go | 116 ++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 12 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index c0012dc0e53..56167819afb 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -5,6 +5,7 @@ package middleware import ( "context" + "errors" "fmt" "log" "os" @@ -32,6 +33,13 @@ var ( } ) +// extensionFailure tracks a failed extension and its startup error. +type extensionFailure struct { + extension *extensions.Extension + err error + timedOut bool +} + type ExtensionsMiddleware struct { extensionManager *extensions.Manager extensionRunner *extensions.Runner @@ -107,7 +115,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A forceColor := !color.NoColor var wg sync.WaitGroup var mu sync.Mutex - var failedExtensions []*extensions.Extension + var failedExtensions []extensionFailure // Track total time for all extensions to become ready allExtensionsStartTime := time.Now() @@ -187,7 +195,11 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A // Track failed extensions for warning display mu.Lock() - failedExtensions = append(failedExtensions, ext) + failedExtensions = append(failedExtensions, extensionFailure{ + extension: ext, + err: err, + timedOut: errors.Is(err, context.DeadlineExceeded), + }) mu.Unlock() } else { elapsed := time.Since(startTime) @@ -199,19 +211,99 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A // Wait for all extensions to reach a terminal state (ready or failed) wg.Wait() - // Check for failed extensions and display warnings - + // Check for failed extensions and display categorized warnings if len(failedExtensions) > 0 { - m.console.Message(ctx, output.WithWarningFormat("WARNING: Extension startup failures detected")) - m.console.Message(ctx, "The following extensions failed to initialize within the timeout period:") - for _, ext := range failedExtensions { - m.console.Message(ctx, fmt.Sprintf(" - %s (%s)", ext.DisplayName, ext.Id)) + type upgradeInfo struct { + ext *extensions.Extension + result *extensions.UpdateCheckResult + } + + var needsUpdate []upgradeInfo + var timedOut []extensionFailure + + cacheManager, cacheErr := extensions.NewRegistryCacheManager() + var upgradeChecker *extensions.UpdateChecker + if cacheErr != nil { + log.Printf("skipping upgrade check for failed extensions (cache unavailable): %v", cacheErr) + } else { + upgradeChecker = extensions.NewUpdateChecker(cacheManager) } + + for _, failure := range failedExtensions { + hasUpdate := false + var upgradeResult *extensions.UpdateCheckResult + + if upgradeChecker != nil { + result, err := upgradeChecker.CheckForUpdate(ctx, failure.extension) + if err != nil { + log.Printf("failed to check for upgrade for '%s': %v", failure.extension.Id, err) + } else if result != nil && result.HasUpdate { + hasUpdate = true + upgradeResult = result + } + } + + if hasUpdate { + needsUpdate = append(needsUpdate, upgradeInfo{failure.extension, upgradeResult}) + } else if failure.timedOut { + timedOut = append(timedOut, failure) + } + } + + // Display upgrade warnings (single vs multiple) + if len(needsUpdate) == 1 { + info := needsUpdate[0] + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: Extension %s needs an upgrade (%s \u2192 %s) and did not start.", + info.ext.Id, info.result.InstalledVersion, info.result.LatestVersion, + )) + m.console.Message(ctx, fmt.Sprintf( + "Run %s", output.WithHighLightFormat("azd extension upgrade %s", info.ext.Id), + )) + m.console.Message(ctx, "") + } else if len(needsUpdate) > 1 { + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: The following extensions need upgrade and did not start.", + )) + for _, info := range needsUpdate { + m.console.Message(ctx, output.WithWarningFormat( + fmt.Sprintf("- %s (%s \u2192 %s)", info.ext.Id, + info.result.InstalledVersion, info.result.LatestVersion), + )) + } + m.console.Message(ctx, fmt.Sprintf( + "Run %s to upgrade a specific extension, or %s to upgrade all extensions.", + output.WithHighLightFormat("azd extension upgrade "), + output.WithHighLightFormat("azd extension upgrade --all"), + )) + m.console.Message(ctx, "") + } + + // Display timeout warnings (single vs multiple) + if len(timedOut) == 1 { + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: Extension %s didn't start due to timeout.", + timedOut[0].extension.Id, + )) + m.console.Message(ctx, "") + } else if len(timedOut) > 1 { + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: The following extensions didn't start due to timeout.", + )) + for _, failure := range timedOut { + m.console.Message(ctx, output.WithWarningFormat( + fmt.Sprintf("- %s", failure.extension.Id), + )) + } + m.console.Message(ctx, "") + } + + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: %d extensions did not start. Their features will be unavailable.", + len(failedExtensions), + )) m.console.Message(ctx, "") - m.console.Message( - ctx, - "Some features may be unavailable. Increase timeout with AZD_EXT_TIMEOUT= if needed.", - ) + m.console.Message(ctx, fmt.Sprintf("Run with %s for details.", output.WithHighLightFormat("--debug"))) m.console.Message(ctx, "") } From ec45e8677921d2f24db380596980774c3574c703 Mon Sep 17 00:00:00 2001 From: hemarina <104857065+hemarina@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:10:28 -0800 Subject: [PATCH 2/6] Update cli/azd/cmd/middleware/extensions.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cli/azd/cmd/middleware/extensions.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 56167819afb..9dfe4529d57 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -298,10 +298,16 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A m.console.Message(ctx, "") } - m.console.Message(ctx, output.WithWarningFormat( - "WARNING: %d extensions did not start. Their features will be unavailable.", - len(failedExtensions), - )) + if len(failedExtensions) == 1 { + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: 1 extension did not start. Its features will be unavailable.", + )) + } else { + m.console.Message(ctx, output.WithWarningFormat( + "WARNING: %d extensions did not start. Their features will be unavailable.", + len(failedExtensions), + )) + } m.console.Message(ctx, "") m.console.Message(ctx, fmt.Sprintf("Run with %s for details.", output.WithHighLightFormat("--debug"))) m.console.Message(ctx, "") From 9c95110aa66d20a727dd29275bc29552d0c5975d Mon Sep 17 00:00:00 2001 From: hemarina Date: Fri, 6 Mar 2026 15:48:15 -0800 Subject: [PATCH 3/6] address feedback --- cli/azd/cmd/middleware/extensions.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 9dfe4529d57..a3835e856d0 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -116,6 +116,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A var wg sync.WaitGroup var mu sync.Mutex var failedExtensions []extensionFailure + var extErrors []error // Track total time for all extensions to become ready allExtensionsStartTime := time.Now() @@ -177,7 +178,9 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } if _, err := m.extensionRunner.Invoke(ctx, ext, options); err != nil { - m.console.Message(ctx, err.Error()) + mu.Lock() + extErrors = append(extErrors, err) + mu.Unlock() ext.Fail(err) } }() @@ -243,6 +246,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } } + // Only add UI warnings for extensions that outdated or timeout if hasUpdate { needsUpdate = append(needsUpdate, upgradeInfo{failure.extension, upgradeResult}) } else if failure.timedOut { @@ -254,7 +258,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A if len(needsUpdate) == 1 { info := needsUpdate[0] m.console.Message(ctx, output.WithWarningFormat( - "WARNING: Extension %s needs an upgrade (%s \u2192 %s) and did not start.", + "WARNING: Extension %s did not start. An update is available (%s \u2192 %s) that may resolve this.", info.ext.Id, info.result.InstalledVersion, info.result.LatestVersion, )) m.console.Message(ctx, fmt.Sprintf( @@ -263,7 +267,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A m.console.Message(ctx, "") } else if len(needsUpdate) > 1 { m.console.Message(ctx, output.WithWarningFormat( - "WARNING: The following extensions need upgrade and did not start.", + "WARNING: The following extensions did not start. Updates are available that may resolve these issues.", )) for _, info := range needsUpdate { m.console.Message(ctx, output.WithWarningFormat( @@ -298,6 +302,13 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A m.console.Message(ctx, "") } + if len(extErrors) > 0 { + for _, e := range extErrors { + m.console.Message(ctx, e.Error()) + } + m.console.Message(ctx, "") + } + if len(failedExtensions) == 1 { m.console.Message(ctx, output.WithWarningFormat( "WARNING: 1 extension did not start. Its features will be unavailable.", From 5df451712c8d9e339fc963126c13df216812411b Mon Sep 17 00:00:00 2001 From: hemarina Date: Mon, 9 Mar 2026 17:07:21 -0700 Subject: [PATCH 4/6] address feedback --- cli/azd/cmd/middleware/extensions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index a3835e856d0..d265fe82746 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -262,7 +262,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A info.ext.Id, info.result.InstalledVersion, info.result.LatestVersion, )) m.console.Message(ctx, fmt.Sprintf( - "Run %s", output.WithHighLightFormat("azd extension upgrade %s", info.ext.Id), + "To upgrade extension, run %s", output.WithHighLightFormat("azd extension upgrade %s", info.ext.Id), )) m.console.Message(ctx, "") } else if len(needsUpdate) > 1 { From b5547a5b43902c5a0d541c62c80e13a0a98c6043 Mon Sep 17 00:00:00 2001 From: hemarina Date: Wed, 11 Mar 2026 16:47:24 -0700 Subject: [PATCH 5/6] address feedback --- cli/azd/cmd/middleware/extensions.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index d265fe82746..3cb9f114d63 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -116,7 +116,6 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A var wg sync.WaitGroup var mu sync.Mutex var failedExtensions []extensionFailure - var extErrors []error // Track total time for all extensions to become ready allExtensionsStartTime := time.Now() @@ -178,9 +177,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } if _, err := m.extensionRunner.Invoke(ctx, ext, options); err != nil { - mu.Lock() - extErrors = append(extErrors, err) - mu.Unlock() + log.Printf(err.Error()) ext.Fail(err) } }() @@ -223,6 +220,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A var needsUpdate []upgradeInfo var timedOut []extensionFailure + var otherFailures []extensionFailure cacheManager, cacheErr := extensions.NewRegistryCacheManager() var upgradeChecker *extensions.UpdateChecker @@ -246,11 +244,12 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } } - // Only add UI warnings for extensions that outdated or timeout if hasUpdate { needsUpdate = append(needsUpdate, upgradeInfo{failure.extension, upgradeResult}) } else if failure.timedOut { timedOut = append(timedOut, failure) + } else { + otherFailures = append(otherFailures, failure) } } @@ -277,7 +276,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } m.console.Message(ctx, fmt.Sprintf( "Run %s to upgrade a specific extension, or %s to upgrade all extensions.", - output.WithHighLightFormat("azd extension upgrade "), + output.WithHighLightFormat("azd extension upgrade "), output.WithHighLightFormat("azd extension upgrade --all"), )) m.console.Message(ctx, "") @@ -302,11 +301,10 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A m.console.Message(ctx, "") } - if len(extErrors) > 0 { - for _, e := range extErrors { - m.console.Message(ctx, e.Error()) + if len(otherFailures) > 0 { + for _, failure := range otherFailures { + log.Printf("Extension '%s' failed to start: %v", failure.extension.Id, failure.err) } - m.console.Message(ctx, "") } if len(failedExtensions) == 1 { From b2f4d341e4f0c42a3de282395c1dc549bfced5d9 Mon Sep 17 00:00:00 2001 From: hemarina Date: Wed, 11 Mar 2026 17:05:05 -0700 Subject: [PATCH 6/6] clean error message --- cli/azd/cmd/middleware/extensions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/azd/cmd/middleware/extensions.go b/cli/azd/cmd/middleware/extensions.go index 3cb9f114d63..5c8dda9191f 100644 --- a/cli/azd/cmd/middleware/extensions.go +++ b/cli/azd/cmd/middleware/extensions.go @@ -177,7 +177,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A } if _, err := m.extensionRunner.Invoke(ctx, ext, options); err != nil { - log.Printf(err.Error()) + log.Printf("%v", err) ext.Fail(err) } }() @@ -295,7 +295,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A )) for _, failure := range timedOut { m.console.Message(ctx, output.WithWarningFormat( - fmt.Sprintf("- %s", failure.extension.Id), + "- %s", failure.extension.Id, )) } m.console.Message(ctx, "") @@ -303,7 +303,7 @@ func (m *ExtensionsMiddleware) Run(ctx context.Context, next NextFn) (*actions.A if len(otherFailures) > 0 { for _, failure := range otherFailures { - log.Printf("Extension '%s' failed to start: %v", failure.extension.Id, failure.err) + log.Printf("%v", failure.err) } }