diff --git a/commands/displayers/functions.go b/commands/displayers/functions.go index a96aae7cf..a10c9e8c7 100644 --- a/commands/displayers/functions.go +++ b/commands/displayers/functions.go @@ -18,12 +18,12 @@ import ( "strings" "time" - "github.com/digitalocean/doctl/do" + "github.com/apache/openwhisk-client-go/whisk" ) // Functions is the type of the displayer for functions list type Functions struct { - Info []do.FunctionInfo + Info []whisk.Action } var _ Displayable = &Functions{} @@ -67,7 +67,7 @@ func (i *Functions) KV() []map[string]interface{} { } // findRuntime finds the runtime string amongst the annotations of a function -func findRuntime(annots []do.Annotation) string { +func findRuntime(annots whisk.KeyValueArr) string { for i := range annots { if annots[i].Key == "exec" { return annots[i].Value.(string) diff --git a/commands/functions.go b/commands/functions.go index 024d27d68..fc53f7722 100644 --- a/commands/functions.go +++ b/commands/functions.go @@ -214,18 +214,33 @@ func RunFunctionsInvoke(c *CmdConfig) error { if err != nil { return err } - // Assemble args and flags except for "param" - args := getFlatArgsArray(c, []string{flagWeb, flagFull, flagNoWait, flagResult}, []string{flagParamFile}) - // Add "param" with special handling if present - args, err = appendParams(c, args) + paramFile, _ := c.Doit.GetString(c.NS, flagParamFile) + paramFlags, _ := c.Doit.GetStringSlice(c.NS, flagParam) + params, err := consolidateParams(paramFile, paramFlags) if err != nil { return err } - output, err := ServerlessExec(c, actionInvoke, args...) + web, _ := c.Doit.GetBool(c.NS, flagWeb) + if web { + var mapParams map[string]interface{} = nil + if params != nil { + p, ok := params.(map[string]interface{}) + if !ok { + return fmt.Errorf("cannot invoke via web: parameters do not form a dictionary") + } + mapParams = p + } + return c.Serverless().InvokeFunctionViaWeb(c.Args[0], mapParams) + } + full, _ := c.Doit.GetBool(c.NS, flagFull) + noWait, _ := c.Doit.GetBool(c.NS, flagNoWait) + blocking := !noWait + result := blocking && !full + response, err := c.Serverless().InvokeFunction(c.Args[0], params, blocking, result) if err != nil { return err } - + output := do.ServerlessOutput{Entity: response} return c.PrintServerlessTextOutput(output) } @@ -257,7 +272,7 @@ func RunFunctionsList(c *CmdConfig) error { if err != nil { return err } - var formatted []do.FunctionInfo + var formatted []whisk.Action err = json.Unmarshal(rawOutput, &formatted) if err != nil { return err @@ -265,23 +280,30 @@ func RunFunctionsList(c *CmdConfig) error { return c.Display(&displayers.Functions{Info: formatted}) } -// appendParams determines if there is a 'param' flag (value is a slice, elements -// of the slice should be in KEY:VALUE form), if so, transforms it into the form -// expected by 'nim' (each param is its own --param flag, KEY and VALUE are separate -// tokens). The 'args' argument is the result of getFlatArgsArray and is appended -// to. -func appendParams(c *CmdConfig, args []string) ([]string, error) { - params, err := c.Doit.GetStringSlice(c.NS, flagParam) - if err != nil || len(params) == 0 { - return args, nil // error here is not considered an error (and probably won't occur) +// consolidateParams accepts parameters from a file, the command line, or both, and consolidates all +// such parameters into a simple dictionary. +func consolidateParams(paramFile string, params []string) (interface{}, error) { + consolidated := map[string]interface{}{} + if len(paramFile) > 0 { + contents, err := os.ReadFile(paramFile) + if err != nil { + return nil, err + } + err = json.Unmarshal(contents, &consolidated) + if err != nil { + return nil, err + } } for _, param := range params { parts := strings.Split(param, ":") if len(parts) < 2 { - return args, errors.New("values for --params must have KEY:VALUE form") + return nil, fmt.Errorf("values for --params must have KEY:VALUE form") } parts1 := strings.Join(parts[1:], ":") - args = append(args, dashdashParam, parts[0], parts1) + consolidated[parts[0]] = parts1 + } + if len(consolidated) > 0 { + return consolidated, nil } - return args, nil + return nil, nil } diff --git a/commands/functions_test.go b/commands/functions_test.go index 148b3cade..1b05a025d 100644 --- a/commands/functions_test.go +++ b/commands/functions_test.go @@ -173,50 +173,57 @@ func TestFunctionsGet(t *testing.T) { func TestFunctionsInvoke(t *testing.T) { tests := []struct { - name string - doctlArgs string - doctlFlags map[string]interface{} - expectedNimArgs []string + name string + doctlArgs string + doctlFlags map[string]interface{} + requestResult bool + passedParams interface{} }{ { - name: "no flags", - doctlArgs: "hello", - expectedNimArgs: []string{"hello"}, + name: "no flags", + doctlArgs: "hello", + requestResult: true, + passedParams: nil, }, { - name: "full flag", - doctlArgs: "hello", - doctlFlags: map[string]interface{}{"full": ""}, - expectedNimArgs: []string{"hello", "--full"}, + name: "full flag", + doctlArgs: "hello", + doctlFlags: map[string]interface{}{"full": ""}, + requestResult: false, + passedParams: nil, }, { - name: "param flag", - doctlArgs: "hello", - doctlFlags: map[string]interface{}{"param": "name:world"}, - expectedNimArgs: []string{"hello", "--param", "name", "world"}, + name: "param flag", + doctlArgs: "hello", + doctlFlags: map[string]interface{}{"param": "name:world"}, + requestResult: true, + passedParams: map[string]interface{}{"name": "world"}, }, { - name: "param flag list", - doctlArgs: "hello", - doctlFlags: map[string]interface{}{"param": []string{"name:world", "address:everywhere"}}, - expectedNimArgs: []string{"hello", "--param", "name", "world", "--param", "address", "everywhere"}, + name: "param flag list", + doctlArgs: "hello", + doctlFlags: map[string]interface{}{"param": []string{"name:world", "address:everywhere"}}, + requestResult: true, + passedParams: map[string]interface{}{"name": "world", "address": "everywhere"}, }, { - name: "param flag colon-value", - doctlArgs: "hello", - doctlFlags: map[string]interface{}{"param": []string{"url:https://example.com"}}, - expectedNimArgs: []string{"hello", "--param", "url", "https://example.com"}, + name: "param flag colon-value", + doctlArgs: "hello", + doctlFlags: map[string]interface{}{"param": []string{"url:https://example.com"}}, + requestResult: true, + passedParams: map[string]interface{}{"url": "https://example.com"}, }, } + expectedRemoteResult := map[string]interface{}{ + "body": "Hello world!", + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { withTestClient(t, func(config *CmdConfig, tm *tcMocks) { buf := &bytes.Buffer{} config.Out = buf - fakeCmd := &exec.Cmd{ - Stdout: config.Out, - } config.Args = append(config.Args, tt.doctlArgs) if tt.doctlFlags != nil { @@ -229,11 +236,7 @@ func TestFunctionsInvoke(t *testing.T) { } } - tm.serverless.EXPECT().CheckServerlessStatus(hashAccessToken(config)).MinTimes(1).Return(nil) - tm.serverless.EXPECT().Cmd("action/invoke", tt.expectedNimArgs).Return(fakeCmd, nil) - tm.serverless.EXPECT().Exec(fakeCmd).Return(do.ServerlessOutput{ - Entity: map[string]interface{}{"body": "Hello world!"}, - }, nil) + tm.serverless.EXPECT().InvokeFunction(tt.doctlArgs, tt.passedParams, true, tt.requestResult).Return(expectedRemoteResult, nil) expectedOut := `{ "body": "Hello world!" } diff --git a/do/mocks/ServerlessService.go b/do/mocks/ServerlessService.go index f650e00f9..31c4fc153 100644 --- a/do/mocks/ServerlessService.go +++ b/do/mocks/ServerlessService.go @@ -200,6 +200,35 @@ func (mr *MockServerlessServiceMockRecorder) InstallServerless(arg0, arg1 interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallServerless", reflect.TypeOf((*MockServerlessService)(nil).InstallServerless), arg0, arg1) } +// InvokeFunction mocks base method. +func (m *MockServerlessService) InvokeFunction(arg0 string, arg1 interface{}, arg2, arg3 bool) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InvokeFunction", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InvokeFunction indicates an expected call of InvokeFunction. +func (mr *MockServerlessServiceMockRecorder) InvokeFunction(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InvokeFunction", reflect.TypeOf((*MockServerlessService)(nil).InvokeFunction), arg0, arg1, arg2, arg3) +} + +// InvokeFunctionViaWeb mocks base method. +func (m *MockServerlessService) InvokeFunctionViaWeb(arg0 string, arg1 map[string]interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InvokeFunctionViaWeb", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InvokeFunctionViaWeb indicates an expected call of InvokeFunctionViaWeb. +func (mr *MockServerlessServiceMockRecorder) InvokeFunctionViaWeb(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InvokeFunctionViaWeb", reflect.TypeOf((*MockServerlessService)(nil).InvokeFunctionViaWeb), arg0, arg1) +} + // ListNamespaces mocks base method. func (m *MockServerlessService) ListNamespaces(arg0 context.Context) (do.NamespaceListResponse, error) { m.ctrl.T.Helper() diff --git a/do/serverless.go b/do/serverless.go index 6945e116c..7ea729c1d 100644 --- a/do/serverless.go +++ b/do/serverless.go @@ -21,6 +21,7 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -31,6 +32,7 @@ import ( "github.com/digitalocean/doctl" "github.com/digitalocean/doctl/pkg/extract" "github.com/digitalocean/godo" + "github.com/pkg/browser" "gopkg.in/yaml.v3" ) @@ -112,25 +114,6 @@ type ServerlessHostInfo struct { Runtimes map[string][]ServerlessRuntime `json:"runtimes"` } -// FunctionInfo is the type of an individual function in the output -// of doctl sls fn list. Only relevant fields are unmarshaled. -// Note: when we start replacing the sandbox plugin path with direct calls -// to backend controller operations, this will be replaced by declarations -// in the golang openwhisk client. -type FunctionInfo struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Updated int64 `json:"updated"` - Version string `json:"version"` - Annotations []Annotation `json:"annotations"` -} - -// Annotation is a key/value type suitable for individual annotations -type Annotation struct { - Key string `json:"key"` - Value interface{} `json:"value"` -} - // ServerlessProject ... type ServerlessProject struct { ProjectPath string `json:"project_path"` @@ -208,6 +191,8 @@ type ServerlessService interface { CheckServerlessStatus(string) error InstallServerless(string, bool) error GetFunction(string, bool) (whisk.Action, []FunctionParameter, error) + InvokeFunction(string, interface{}, bool, bool) (map[string]interface{}, error) + InvokeFunctionViaWeb(string, map[string]interface{}) error GetConnectedAPIHost() (string, error) ReadProject(*ServerlessProject, []string) (ServerlessOutput, error) WriteProject(ServerlessProject) (string, error) @@ -671,6 +656,62 @@ func (s *serverlessService) GetFunction(name string, fetchCode bool) (whisk.Acti return *action, parameters, err } +// InvokeFunction invokes a function via POST with authentication +func (s *serverlessService) InvokeFunction(name string, params interface{}, blocking bool, result bool) (map[string]interface{}, error) { + var empty map[string]interface{} + err := initWhisk(s) + if err != nil { + return empty, err + } + resp, _, err := s.owClient.Actions.Invoke(name, params, blocking, result) + return resp, err +} + +// InvokeFunctionViaWeb invokes a function via GET using its web URL (or error if not a web function) +func (s *serverlessService) InvokeFunctionViaWeb(name string, params map[string]interface{}) error { + // Get the function so we can use its metadata in formulating the request + theFunction, _, err := s.GetFunction(name, false) + if err != nil { + return err + } + // Check that it's a web function + isWeb := false + for _, annot := range theFunction.Annotations { + if annot.Key == "web-export" { + isWeb = true + break + } + } + if !isWeb { + return fmt.Errorf("'%s' is not a web function", name) + } + // Formulate the invocation URL + host, err := s.GetConnectedAPIHost() + if err != nil { + return err + } + nsParts := strings.Split(theFunction.Namespace, "/") + namespace := nsParts[0] + pkg := "default" + if len(nsParts) > 1 { + pkg = nsParts[1] + } + theURL := fmt.Sprintf("%s/api/v1/web/%s/%s/%s", host, namespace, pkg, theFunction.Name) + // Add params, if any + if params != nil { + encoded := url.Values{} + for key, val := range params { + stringVal, ok := val.(string) + if !ok { + return fmt.Errorf("the value of '%s' is not a string; web invocation is not possible", key) + } + encoded.Add(key, stringVal) + } + theURL += "?" + encoded.Encode() + } + return browser.OpenURL(theURL) +} + // GetConnectedAPIHost retrieves the API host to which the service is currently connected func (s *serverlessService) GetConnectedAPIHost() (string, error) { err := initWhisk(s) diff --git a/go.mod b/go.mod index 0678b3e7b..360bee103 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,11 @@ require ( sigs.k8s.io/yaml v1.2.0 ) -require github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b +require ( + github.com/apache/openwhisk-client-go v0.0.0-20211007130743-38709899040b + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) require ( github.com/Microsoft/go-winio v0.5.2 // indirect @@ -86,7 +90,6 @@ require ( google.golang.org/appengine v1.6.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect diff --git a/go.sum b/go.sum index 22499a7d3..961828674 100644 --- a/go.sum +++ b/go.sum @@ -639,6 +639,8 @@ github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1003,6 +1005,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vendor/github.com/pkg/browser/LICENSE b/vendor/github.com/pkg/browser/LICENSE new file mode 100644 index 000000000..65f78fb62 --- /dev/null +++ b/vendor/github.com/pkg/browser/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/browser/README.md b/vendor/github.com/pkg/browser/README.md new file mode 100644 index 000000000..72b1976e3 --- /dev/null +++ b/vendor/github.com/pkg/browser/README.md @@ -0,0 +1,55 @@ + +# browser + import "github.com/pkg/browser" + +Package browser provides helpers to open files, readers, and urls in a browser window. + +The choice of which browser is started is entirely client dependant. + + + + + +## Variables +``` go +var Stderr io.Writer = os.Stderr +``` +Stderr is the io.Writer to which executed commands write standard error. + +``` go +var Stdout io.Writer = os.Stdout +``` +Stdout is the io.Writer to which executed commands write standard output. + + +## func OpenFile +``` go +func OpenFile(path string) error +``` +OpenFile opens new browser window for the file path. + + +## func OpenReader +``` go +func OpenReader(r io.Reader) error +``` +OpenReader consumes the contents of r and presents the +results in a new browser window. + + +## func OpenURL +``` go +func OpenURL(url string) error +``` +OpenURL opens a new browser window pointing to url. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/vendor/github.com/pkg/browser/browser.go b/vendor/github.com/pkg/browser/browser.go new file mode 100644 index 000000000..d7969d74d --- /dev/null +++ b/vendor/github.com/pkg/browser/browser.go @@ -0,0 +1,57 @@ +// Package browser provides helpers to open files, readers, and urls in a browser window. +// +// The choice of which browser is started is entirely client dependant. +package browser + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" +) + +// Stdout is the io.Writer to which executed commands write standard output. +var Stdout io.Writer = os.Stdout + +// Stderr is the io.Writer to which executed commands write standard error. +var Stderr io.Writer = os.Stderr + +// OpenFile opens new browser window for the file path. +func OpenFile(path string) error { + path, err := filepath.Abs(path) + if err != nil { + return err + } + return OpenURL("file://" + path) +} + +// OpenReader consumes the contents of r and presents the +// results in a new browser window. +func OpenReader(r io.Reader) error { + f, err := ioutil.TempFile("", "browser.*.html") + if err != nil { + return fmt.Errorf("browser: could not create temporary file: %v", err) + } + if _, err := io.Copy(f, r); err != nil { + f.Close() + return fmt.Errorf("browser: caching temporary file failed: %v", err) + } + if err := f.Close(); err != nil { + return fmt.Errorf("browser: caching temporary file failed: %v", err) + } + return OpenFile(f.Name()) +} + +// OpenURL opens a new browser window pointing to url. +func OpenURL(url string) error { + return openBrowser(url) +} + +func runCmd(prog string, args ...string) error { + cmd := exec.Command(prog, args...) + cmd.Stdout = Stdout + cmd.Stderr = Stderr + return cmd.Run() +} diff --git a/vendor/github.com/pkg/browser/browser_darwin.go b/vendor/github.com/pkg/browser/browser_darwin.go new file mode 100644 index 000000000..8507cf7c2 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_darwin.go @@ -0,0 +1,5 @@ +package browser + +func openBrowser(url string) error { + return runCmd("open", url) +} diff --git a/vendor/github.com/pkg/browser/browser_freebsd.go b/vendor/github.com/pkg/browser/browser_freebsd.go new file mode 100644 index 000000000..4fc7ff076 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_freebsd.go @@ -0,0 +1,14 @@ +package browser + +import ( + "errors" + "os/exec" +) + +func openBrowser(url string) error { + err := runCmd("xdg-open", url) + if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { + return errors.New("xdg-open: command not found - install xdg-utils from ports(8)") + } + return err +} diff --git a/vendor/github.com/pkg/browser/browser_linux.go b/vendor/github.com/pkg/browser/browser_linux.go new file mode 100644 index 000000000..d26cdddf9 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_linux.go @@ -0,0 +1,21 @@ +package browser + +import ( + "os/exec" + "strings" +) + +func openBrowser(url string) error { + providers := []string{"xdg-open", "x-www-browser", "www-browser"} + + // There are multiple possible providers to open a browser on linux + // One of them is xdg-open, another is x-www-browser, then there's www-browser, etc. + // Look for one that exists and run it + for _, provider := range providers { + if _, err := exec.LookPath(provider); err == nil { + return runCmd(provider, url) + } + } + + return &exec.Error{Name: strings.Join(providers, ","), Err: exec.ErrNotFound} +} diff --git a/vendor/github.com/pkg/browser/browser_netbsd.go b/vendor/github.com/pkg/browser/browser_netbsd.go new file mode 100644 index 000000000..65a5e5a29 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_netbsd.go @@ -0,0 +1,14 @@ +package browser + +import ( + "errors" + "os/exec" +) + +func openBrowser(url string) error { + err := runCmd("xdg-open", url) + if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { + return errors.New("xdg-open: command not found - install xdg-utils from pkgsrc(7)") + } + return err +} diff --git a/vendor/github.com/pkg/browser/browser_openbsd.go b/vendor/github.com/pkg/browser/browser_openbsd.go new file mode 100644 index 000000000..4fc7ff076 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_openbsd.go @@ -0,0 +1,14 @@ +package browser + +import ( + "errors" + "os/exec" +) + +func openBrowser(url string) error { + err := runCmd("xdg-open", url) + if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { + return errors.New("xdg-open: command not found - install xdg-utils from ports(8)") + } + return err +} diff --git a/vendor/github.com/pkg/browser/browser_unsupported.go b/vendor/github.com/pkg/browser/browser_unsupported.go new file mode 100644 index 000000000..7c5c17d34 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_unsupported.go @@ -0,0 +1,12 @@ +// +build !linux,!windows,!darwin,!openbsd,!freebsd,!netbsd + +package browser + +import ( + "fmt" + "runtime" +) + +func openBrowser(url string) error { + return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS) +} diff --git a/vendor/github.com/pkg/browser/browser_windows.go b/vendor/github.com/pkg/browser/browser_windows.go new file mode 100644 index 000000000..63e192959 --- /dev/null +++ b/vendor/github.com/pkg/browser/browser_windows.go @@ -0,0 +1,7 @@ +package browser + +import "golang.org/x/sys/windows" + +func openBrowser(url string) error { + return windows.ShellExecute(0, nil, windows.StringToUTF16Ptr(url), nil, nil, windows.SW_SHOWNORMAL) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index eb0571d20..3bce0abc1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -185,6 +185,9 @@ github.com/pelletier/go-toml/v2 github.com/pelletier/go-toml/v2/internal/ast github.com/pelletier/go-toml/v2/internal/danger github.com/pelletier/go-toml/v2/internal/tracker +# github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 +## explicit; go 1.14 +github.com/pkg/browser # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors