package prereqs import ( "context" "os/exec" "runtime" "strconv" "strings" "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) type platform struct { os string arch string majorVersion int minorVersion int } var ( // execShellOutput has execShellOutputFunc as the default but can be changed for testing purposes. execShellOutput = execShellOutputFunc runtimeOS = runtime.GOOS runtimeArch = runtime.GOARCH ) // execShellOutputFunc passes a command and args to exec.CommandContext and returns the result as a string func execShellOutputFunc(ctx context.Context, command string, args ...string) (string, error) { result, err := exec.CommandContext(ctx, command, args...).Output() // #nosec G204 if err != nil { return "", errors.Wrap(err, "error in command execution") } return string(result), nil } func supportedPlatforms() []platform { return []platform{ {os: "linux", arch: "amd64"}, {os: "linux", arch: "arm64"}, {os: "darwin", arch: "amd64", majorVersion: 10, minorVersion: 14}, {os: "darwin", arch: "arm64", majorVersion: 12, minorVersion: 5}, {os: "windows", arch: "amd64"}, } } // parseVersion takes a string and splits it using sep separator, and outputs a slice of integers // corresponding to version numbers. If it cannot find num level of versions, it returns an error func parseVersion(input string, num int, sep string) ([]int, error) { var version = make([]int, num) components := strings.Split(input, sep) for i, component := range components { components[i] = strings.TrimSpace(component) } if len(components) < num { return nil, errors.New("insufficient information about version") } for i := range version { var err error version[i], err = strconv.Atoi(components[i]) if err != nil { return nil, errors.Wrap(err, "error during conversion") } } return version, nil } // meetsMinPlatformReqs returns true if the runtime matches any on the list of supported platforms func meetsMinPlatformReqs(ctx context.Context) (bool, error) { okPlatforms := supportedPlatforms() for _, platform := range okPlatforms { if runtimeOS == platform.os && runtimeArch == platform.arch { // If MacOS we make sure it meets the minimum version cutoff if runtimeOS == "darwin" { versionStr, err := execShellOutput(ctx, "uname", "-r") if err != nil { return false, errors.Wrap(err, "error obtaining MacOS version") } version, err := parseVersion(versionStr, 2, ".") if err != nil { return false, errors.Wrap(err, "error parsing version") } if version[0] != platform.majorVersion { return version[0] > platform.majorVersion, nil } if version[1] < platform.minorVersion { return false, nil } return true, nil } // Otherwise we have a match between runtime and our list of accepted platforms return true, nil } } return false, nil } // WarnIfPlatformNotSupported warns if the user's platform is not supported or if it fails to detect user's platform func WarnIfPlatformNotSupported(ctx context.Context) { supported, err := meetsMinPlatformReqs(ctx) if err != nil { log.WithError(err).Warn("Failed to detect host platform") return } if !supported { log.Warn("This platform is not supported. The following platforms are supported: Linux/AMD64," + " Linux/ARM64, Mac OS X/AMD64 (10.14+ only), and Windows/AMD64") } }