Files
opentf/internal/command/webbrowser/exec.go
Martin Atkins dd8c6f5db6 main: Honor the BROWSER environment variable on Unix systems
Prevously OpenTofu delegated browser launching entirely to the third-party
module github.com/cli/browser, which consists of a number of
platform-specific lists of executable commands to try to run to launch
a web browser.

On Unix systems there is also a de-facto convention of using an environment
variable called BROWSER to explicitly specify what to launch. That variable
can either point directly to a browser, or can point to a script which
implements some more complex policy for choosing a browser, such as
detecting whether the command is running in a GUI context and launching
either a GUI or textmode browser.

The BROWSER variable has been most commonly implemented with similar
treatment to earlier variables like EDITOR and PAGER where it's expected
to be set to just a single command to run, with the URL given as the first
and only argument. There was also an attempt to define a more complex
interpretation of this variable at http://www.catb.org/~esr/BROWSER/ , but
that extended treatment was only implemented in a small amount of software,
and those which implemented it did so slightly inconsistently due to the
specification being ambiguous.

OpenTofu's implementation therefore follows the common simpler convention,
but will silently ignore variable values it cannot use so that OpenTofu
won't fail when run in an environment that has that variable set in a way
that's intended for use by some other software. In that case OpenTofu
will continue to perform the default behavior as implemented in the
third-party library.

Because this convention is Unix-specific, OpenTofu will check for and use
this environment variable only on operating systems that the Go toolchain
considers to be "unix". This means that in particular on Windows systems
OpenTofu will continue to follow the Windows convention of specifying
the default browser via an entry in the Windows Registry.

As usual with this sort of system-integration mechanism it isn't really
viable to test this end-to-end in a portable way, but the main logic is
separated out into testable functions, and I manually tested this on my
own Linux system to verify that it works in a real OpenTofu executable.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-11-03 11:27:13 -08:00

85 lines
2.8 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package webbrowser
import (
"fmt"
"os"
"os/exec"
)
// NewExecLauncher creates and returns a Launcher that just attempts to run
// the executable at the given path, with the given URL as its first and
// only argument.
//
// The given path must be ready to use, without reference to the PATH
// environment variable. The caller can use [exec.LookPath] to prepare
// a suitable path if searching PATH is appropriate.
//
// This is intended to allow overriding which browser to use using the
// BROWSER environment variable on Unix-like systems, but the rules for
// that are in "package main". [ParseBrowserEnv] implements parsing of the
// value of that environment variable when the main package decides it's
// appropriate to do so.
func NewExecLauncher(execPath string) Launcher {
return execLauncher{
execPath: execPath,
}
}
type execLauncher struct {
execPath string
}
func (l execLauncher) OpenURL(url string) error {
cmd := &exec.Cmd{
Path: l.execPath,
Args: []string{l.execPath, url},
Env: os.Environ(),
}
err := cmd.Run()
if err != nil {
return fmt.Errorf("%s: %w", l.execPath, err)
}
return nil
}
// ParseBrowserEnv takes the raw value of a BROWSER environment variable and
// attempts to parse it as a reference to an executable, whose absolute
// path is returned if successful. Returns an empty string if the value cannot
// be interpreted as an executable to run.
//
// This implements the simple form of this environment variable commonly used
// by software on Unix-like systems, where the value must be literally just
// a command to run whose first and only argument would be the URL to open.
//
// It does NOT support the more complex interpretation of that environment
// variable that was proposed at http://www.catb.org/~esr/BROWSER/ , because
// that form has not been widely implemented and the implementations that
// exist do not have consistent behavior due to the proposal being
// ambiguous.
//
// Callers that use this should typically pass a successful result to
// [NewExecLauncher] to use the resolved command as a browser launcher. The
// caller is responsible for deciding the policy for whether to consider a
// BROWSER environment variable and for accessing the environment table to
// obtain its value.
func ParseBrowserEnv(raw string) string {
if raw == "" {
return "" // empty is treated the same as unset
}
execPath, err := exec.LookPath(raw)
if err != nil {
// We silently ignore variable values we cannot use, because this
// environment variable is not OpenTofu-specific and so it may have
// been set for the benefit of software other than OpenTofu which
// interprets it differently.
return ""
}
return execPath
}