diff --git a/commands/namespaces.go b/commands/namespaces.go new file mode 100644 index 000000000..13c85aa8a --- /dev/null +++ b/commands/namespaces.go @@ -0,0 +1,48 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/openfaas/faas-cli/proxy" + "github.com/spf13/cobra" +) + +func init() { + // Setup flags that are used by multiple commands (variables defined in faas.go) + namespacesCmd.Flags().StringVarP(&gateway, "gateway", "g", defaultGateway, "Gateway URL starting with http(s)://") + namespacesCmd.Flags().BoolVar(&tlsInsecure, "tls-no-verify", false, "Disable TLS validation") + namespacesCmd.Flags().StringVarP(&token, "token", "k", "", "Pass a JWT token to use instead of basic auth") + + faasCmd.AddCommand(namespacesCmd) +} + +var namespacesCmd = &cobra.Command{ + Use: `namespaces [--gateway GATEWAY_URL] [--tls-no-verify] [--token JWT_TOKEN]`, + Aliases: []string{"ns"}, + Short: "List OpenFaaS namespaces", + Long: `Lists OpenFaaS namespaces either on a local or remote gateway`, + Example: ` faas-cli namespaces + faas-cli namespaces --gateway https://127.0.0.1:8080`, + RunE: runNamespaces, +} + +func runNamespaces(cmd *cobra.Command, args []string) error { + gatewayAddress := getGatewayURL(gateway, defaultGateway, "", os.Getenv(openFaaSURLEnvironment)) + + namespaces, err := proxy.ListNamespacesToken(gatewayAddress, tlsInsecure, token) + if err != nil { + return err + } + + printNamespaces(namespaces) + + return nil +} + +func printNamespaces(namespaces []string) { + fmt.Print("Namespaces:\n") + for _, v := range namespaces { + fmt.Printf(" - %s\n", v) + } +} diff --git a/proxy/namespaces.go b/proxy/namespaces.go new file mode 100644 index 000000000..cf62bf944 --- /dev/null +++ b/proxy/namespaces.go @@ -0,0 +1,73 @@ +package proxy + +import ( + "encoding/json" + + "fmt" + "io/ioutil" + "net/http" + "strings" +) + +// ListNamespaces lists available function namespaces +func ListNamespaces(gateway string, tlsInsecure bool) ([]string, error) { + return ListNamespacesToken(gateway, tlsInsecure, "") +} + +// ListNamespacesToken lists available function namespaces with a token as auth +func ListNamespacesToken(gateway string, tlsInsecure bool, token string) ([]string, error) { + var namespaces []string + + gateway = strings.TrimRight(gateway, "/") + client := MakeHTTPClient(&defaultCommandTimeout, tlsInsecure) + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } + + getEndpoint, err := createNamespacesEndpoint(gateway) + if err != nil { + return namespaces, err + } + + getRequest, err := http.NewRequest(http.MethodGet, getEndpoint, nil) + + if len(token) > 0 { + SetToken(getRequest, token) + } else { + SetAuth(getRequest, gateway) + } + + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + } + + res, err := client.Do(getRequest) + if err != nil { + return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", gateway) + } + + if res.Body != nil { + defer res.Body.Close() + } + + switch res.StatusCode { + case http.StatusOK: + + bytesOut, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("cannot read namespaces from OpenFaaS on URL: %s", gateway) + } + jsonErr := json.Unmarshal(bytesOut, &namespaces) + if jsonErr != nil { + return nil, fmt.Errorf("cannot parse namespaces from OpenFaaS on URL: %s\n%s", gateway, jsonErr.Error()) + } + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server") + default: + bytesOut, err := ioutil.ReadAll(res.Body) + if err == nil { + return nil, fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(bytesOut)) + } + } + return namespaces, nil +} diff --git a/proxy/utils.go b/proxy/utils.go index b7b41c386..dd5ecc652 100644 --- a/proxy/utils.go +++ b/proxy/utils.go @@ -7,8 +7,9 @@ import ( ) const ( - systemPath = "/system/functions" - functionPath = "/system/function" + systemPath = "/system/functions" + functionPath = "/system/function" + namespacesPath = "/system/namespaces" ) func createSystemEndpoint(gateway, namespace string) (string, error) { @@ -38,3 +39,12 @@ func createFunctionEndpoint(gateway, functionName, namespace string) (string, er } return gatewayURL.String(), nil } + +func createNamespacesEndpoint(gateway string) (string, error) { + gatewayURL, err := url.Parse(gateway) + if err != nil { + return "", fmt.Errorf("invalid gateway URL: %s", err.Error()) + } + gatewayURL.Path = path.Join(gatewayURL.Path, namespacesPath) + return gatewayURL.String(), nil +}