cli/azd/cmd/util.go (126 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package cmd
import (
"context"
"errors"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/azure/azure-dev/cli/azd/internal/tracing"
azdExec "github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/cli/browser"
)
// CmdAnnotations on a command
type CmdAnnotations map[string]string
type Asker func(p survey.Prompt, response interface{}) error
func serviceNameWarningCheck(console input.Console, serviceNameFlag string, commandName string) {
if serviceNameFlag == "" {
return
}
fmt.Fprintln(
console.Handles().Stderr,
output.WithWarningFormat("WARNING: The `--service` flag is deprecated and will be removed in a future release."),
)
fmt.Fprintf(console.Handles().Stderr, "Next time use `azd %s <service>`.\n\n", commandName)
}
func getTargetServiceName(
ctx context.Context,
projectManager project.ProjectManager,
importManager *project.ImportManager,
projectConfig *project.ProjectConfig,
commandName string,
targetServiceName string,
allFlagValue bool,
) (string, error) {
if allFlagValue && targetServiceName != "" {
return "", fmt.Errorf("cannot specify both --all and <service>")
}
if !allFlagValue && targetServiceName == "" {
targetService, err := projectManager.DefaultServiceFromWd(ctx, projectConfig)
if errors.Is(err, project.ErrNoDefaultService) {
return "", fmt.Errorf(
"current working directory is not a project or service directory. Specify a service name to %s a service, "+
"or specify --all to %s all services",
commandName,
commandName,
)
} else if err != nil {
return "", err
}
if targetService != nil {
targetServiceName = targetService.Name
}
}
if targetServiceName != "" {
if has, err := importManager.HasService(ctx, projectConfig, targetServiceName); err != nil {
return "", err
} else if !has {
return "", fmt.Errorf("service name '%s' doesn't exist", targetServiceName)
}
}
return targetServiceName, nil
}
// Calculate the total time since t, excluding user interaction time.
func since(t time.Time) time.Duration {
userInteractTime := tracing.InteractTimeMs.Load()
return time.Since(t) - time.Duration(userInteractTime)*time.Millisecond
}
// BrowseUrl allow users to override the default browser from the cmd package with some external implementation.
type browseUrl func(ctx context.Context, console input.Console, url string)
// OverrideBrowser allows users to set their own implementation for browsing urls.
var overrideBrowser browseUrl
func openWithDefaultBrowser(ctx context.Context, console input.Console, url string) {
if overrideBrowser != nil {
overrideBrowser(ctx, console, url)
return
}
cmdRunner := azdExec.NewCommandRunner(nil)
// In Codespaces and devcontainers a $BROWSER environment variable is
// present whose value is an executable that launches the browser when
// called with the form:
// $BROWSER <url>
const BrowserEnvVarName = "BROWSER"
if envBrowser := os.Getenv(BrowserEnvVarName); len(envBrowser) > 0 {
_, err := cmdRunner.Run(ctx, azdExec.RunArgs{
Cmd: envBrowser,
Args: []string{url},
})
if err == nil {
return
}
log.Printf(
"warning: failed to open browser configured by $BROWSER: %s\nTrying with default browser.\n",
err.Error(),
)
}
err := browser.OpenURL(url)
if err == nil {
return
}
log.Printf(
"warning: failed to open default browser: %s\nTrying manual launch.", err.Error(),
)
// wsl manual launch. Trying cmd first, and pwsh second
_, err = cmdRunner.Run(ctx, azdExec.RunArgs{
Cmd: "cmd.exe",
// cmd notes:
// /c -> run command
// /s -> quoted string, use the text within the quotes as it is
// Replace `&` for `^&` -> cmd require to scape the & to avoid using it as a command concat
Args: []string{
"/s", "/c", fmt.Sprintf("start %s", strings.ReplaceAll(url, "&", "^&")),
},
})
if err == nil {
return
}
log.Printf(
"warning: failed to open browser with cmd: %s\nTrying powershell.", err.Error(),
)
_, err = cmdRunner.Run(ctx, azdExec.RunArgs{
Cmd: "powershell.exe",
Args: []string{
"-NoProfile", "-Command", "Start-Process", fmt.Sprintf("\"%s\"", url),
},
})
if err == nil {
return
}
log.Printf("warning: failed to use manual launch: %s\n", err.Error())
console.Message(ctx, fmt.Sprintf("Azd was unable to open the next url. Please try it manually: %s", url))
}
type envFlagKey string
// envFlagCtxKey is the context key for internal.EnvFlag
var envFlagCtxKey envFlagKey = "envFlag"
const referenceDocumentationUrl = "https://learn.microsoft.com/azure/developer/azure-developer-cli/reference#"