Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions cli/azd/cmd/auto_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,19 +421,20 @@ func ExecuteWithAutoInstall(ctx context.Context, rootContainer *ioc.NestedContai
// Known command, proceed with normal execution
err := rootCmd.ExecuteContext(ctx)

// Only attempt service-host auto-install when the command failed with that specific error.
// Other command errors (for example, unsupported output formats) should be returned directly.
unsupportedErr, ok := errors.AsType[*project.UnsupportedServiceHostError](err)
if !ok {
return err
}

if err := rootContainer.Resolve(&extensionManager); err != nil {
log.Panic("failed to resolve extension manager for auto-install:", err)
}
if err := rootContainer.Resolve(&console); err != nil {
log.Panic("failed to resolve console for unknown flags error:", err)
}

// auto-install for target service
unsupportedErr, ok := errors.AsType[*project.UnsupportedServiceHostError](err)
if !ok {
return err
}

requiredHost := unsupportedErr.Host
availableExtensionsForHost, err := extensionManager.FindExtensions(ctx, &extensions.FilterOptions{
Capability: extensions.ServiceTargetProviderCapability,
Expand Down
49 changes: 49 additions & 0 deletions cli/azd/cmd/auto_install_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
package cmd

import (
"io"
"os"
"slices"
"testing"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/runcontext/agentdetect"
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -77,6 +79,53 @@ func TestExecuteWithAutoInstallIntegration(t *testing.T) {
os.Args = originalArgs
}

func TestExecuteWithAutoInstall_ReturnsCommandErrorWithoutPanicForOutputFlags(t *testing.T) {
originalArgs := os.Args
defer func() {
os.Args = originalArgs
}()

clearAgentEnvVarsForTest(t)
agentdetect.ResetDetection()
defer agentdetect.ResetDetection()

os.Args = []string{
"azd",
"auth",
"token",
"--scope",
"https://graph.microsoft.com/.default",
"--tenant-id",
"00000000-0000-0000-0000-000000000000",
Comment thread
weikanglim marked this conversation as resolved.
"--output",
"none",
}

rootContainer := ioc.NewNestedContainer(nil)
stderrReader, stderrWriter, err := os.Pipe()
require.NoError(t, err)

originalStderr := os.Stderr
os.Stderr = stderrWriter
defer func() {
os.Stderr = originalStderr
}()

var execErr error
require.NotPanics(t, func() {
execErr = ExecuteWithAutoInstall(t.Context(), rootContainer)
})

require.NoError(t, stderrWriter.Close())

stderrBytes, err := io.ReadAll(stderrReader)
require.NoError(t, err)

require.ErrorContains(t, execErr, "unsupported format 'none'")
Comment thread
weikanglim marked this conversation as resolved.
require.NotContains(t, string(stderrBytes), "panic:")
require.Contains(t, string(stderrBytes), "Error: unsupported format 'none'")
}

// TestAgentDetectionIntegration tests the full agent detection integration flow.
func TestAgentDetectionIntegration(t *testing.T) {
tests := []struct {
Expand Down
10 changes: 9 additions & 1 deletion cli/azd/cmd/cobra_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ func (cb *CobraBuilder) configureActionResolver(cmd *cobra.Command, descriptor *
actionName := createActionName(cmd)
_, err = middlewareRunner.RunAction(ctx, runOptions, actionName)

// At this point, we know that there might be an error, so we can silence cobra from showing it after us.
// Only silence Cobra if the command-scoped console can resolve. When console resolution fails
// (for example, because output formatter validation failed before UX middleware ran), we want
// Cobra to print the raw error instead of suppressing it.
if err != nil {
var console input.Console
cmd.SilenceErrors = cmdContainer.Resolve(&console) == nil
return err
}

cmd.SilenceErrors = true

return err
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/pkg/output/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func GetCommandFormatter(cmd *cobra.Command) (Formatter, error) {

supported := slices.Contains(supportedFormatters, desiredFormatter)
if !supported {
return nil, fmt.Errorf("unsupported format '%s'", desiredFormatter)
return nil, fmt.Errorf("unsupported format '%s' for --output", desiredFormatter)
}

// Check for --query flag and validate it requires JSON output
Expand Down
Loading