cli/azd/pkg/tools/tool.go (72 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package tools import ( "context" "errors" "fmt" osexec "os/exec" "regexp" "strconv" "github.com/azure/azure-dev/cli/azd/pkg/exec" "github.com/blang/semver/v4" ) type ExternalTool interface { CheckInstalled(ctx context.Context) error InstallUrl() string Name() string } type ErrSemver struct { ToolName string VersionInfo VersionInfo } type VersionInfo struct { MinimumVersion semver.Version UpdateCommand string } func (err *ErrSemver) Error() string { return fmt.Sprintf("need at least version %s or later of %s installed. %s %s version", err.VersionInfo.MinimumVersion.String(), err.ToolName, err.VersionInfo.UpdateCommand, err.ToolName) } // toolInPath checks to see if a program can be found on the PATH, as exec.LookPath // does, returns exec.ErrNotFound in the case where os.LookPath would return // exec.ErrNotFound and other errors. func ToolInPath(name string) error { _, err := osexec.LookPath(name) switch { case err == nil: return nil case errors.Is(err, osexec.ErrNotFound): return osexec.ErrNotFound default: return fmt.Errorf("failed searching for `%s` on PATH: %w", name, err) } } func ExecuteCommand(ctx context.Context, commandRunner exec.CommandRunner, cmd string, args ...string) (string, error) { runResult, err := commandRunner.Run(ctx, exec.RunArgs{ Cmd: cmd, Args: args, }) return runResult.Stdout, err } // ExtractVersion extracts a major.minor.patch version number from a typical CLI version flag output. // // minor and patch version numbers are both optional, treated as 0 if not found. func ExtractVersion(cliOutput string) (semver.Version, error) { majorMinorPatch := regexp.MustCompile(`\d+\.\d+\.\d+`).FindString(cliOutput) ver, err := semver.Parse(majorMinorPatch) if err == nil { return ver, nil } majorMinor := regexp.MustCompile(`(\d+)\.(\d+)`).FindStringSubmatch(cliOutput) if len(majorMinor) >= 3 { return semver.Version{ Major: parseUint(majorMinor[1]), Minor: parseUint(majorMinor[2]), }, nil } major := regexp.MustCompile(`\d+`).FindString(cliOutput) if major != "" { return semver.Version{Major: parseUint(major)}, nil } return semver.Version{}, fmt.Errorf("no valid version number found in %s", cliOutput) } func parseUint(s string) uint64 { res, err := strconv.ParseUint(s, 10, 64) if err != nil { panic(err) } return res }