diff --git a/commands/faas.go b/commands/faas.go index 709a811b..16c1355d 100644 --- a/commands/faas.go +++ b/commands/faas.go @@ -15,6 +15,7 @@ import ( "syscall" "github.com/moby/term" + "github.com/openfaas/faas-cli/stack" "github.com/openfaas/faas-cli/version" "github.com/spf13/cobra" ) @@ -47,6 +48,9 @@ var ( tlsInsecure bool ) +// Services parsed from stack file +var services *stack.Services + var stat = func(filename string) (os.FileInfo, error) { return os.Stat(filename) } diff --git a/commands/general.go b/commands/general.go index eb0e8d49..c8cbb54e 100644 --- a/commands/general.go +++ b/commands/general.go @@ -2,13 +2,14 @@ package commands import ( "crypto/tls" + "fmt" "net" "net/http" "net/url" "os" "time" - "github.com/openfaas/faas-cli/proxy" + "github.com/openfaas/faas-cli/config" "github.com/openfaas/go-sdk" ) @@ -43,27 +44,78 @@ func GetDefaultCLITransport(tlsInsecure bool, timeout *time.Duration) *http.Tran } func GetDefaultSDKClient() (*sdk.Client, error) { - gatewayAddress := getGatewayURL(gateway, defaultGateway, "", os.Getenv(openFaaSURLEnvironment)) + var yamlUrl string + if services != nil { + yamlUrl = services.Provider.GatewayURL + } + + gatewayAddress := getGatewayURL(gateway, defaultGateway, yamlUrl, os.Getenv(openFaaSURLEnvironment)) gatewayURL, err := url.Parse(gatewayAddress) if err != nil { return nil, err } - transport := GetDefaultCLITransport(tlsInsecure, &commandTimeout) + authConfig, err := config.LookupAuthConfig(gatewayURL.String()) + if err != nil { + fmt.Printf("Failed to lookup auth config: %s\n", err) + } + + var clientAuth sdk.ClientAuth + var functionTokenSource sdk.TokenSource + if authConfig.Auth == config.BasicAuthType { + username, password, err := config.DecodeAuth(authConfig.Token) + if err != nil { + return nil, err + } + + clientAuth = &sdk.BasicAuth{ + Username: username, + Password: password, + } + } + + if authConfig.Auth == config.Oauth2AuthType { + tokenAuth := &StaticTokenAuth{ + token: authConfig.Token, + } + + clientAuth = tokenAuth + functionTokenSource = tokenAuth + } + + // User specified token gets priority + if len(token) > 0 { + tokenAuth := &StaticTokenAuth{ + token: token, + } + + clientAuth = tokenAuth + functionTokenSource = tokenAuth + } httpClient := &http.Client{} httpClient.Timeout = commandTimeout + transport := GetDefaultCLITransport(tlsInsecure, &commandTimeout) if transport != nil { httpClient.Transport = transport } - clientAuth, err := proxy.NewCLIAuth(token, gatewayAddress) - if err != nil { - return nil, err - } + return sdk.NewClientWithOpts(gatewayURL, httpClient, + sdk.WithAuthentication(clientAuth), + sdk.WithFunctionTokenSource(functionTokenSource), + ), nil +} + +type StaticTokenAuth struct { + token string +} - client := sdk.NewClient(gatewayURL, clientAuth, http.DefaultClient) +func (a *StaticTokenAuth) Set(req *http.Request) error { + req.Header.Add("Authorization", "Bearer "+a.token) + return nil +} - return client, nil +func (ts *StaticTokenAuth) Token() (string, error) { + return ts.token, nil } diff --git a/commands/invoke.go b/commands/invoke.go index 66d663bf..77716ff8 100644 --- a/commands/invoke.go +++ b/commands/invoke.go @@ -4,14 +4,19 @@ package commands import ( + "bytes" "encoding/hex" "fmt" "io" + "net/http" + "net/url" "os" + "runtime" + "strings" "github.com/alexellis/hmac" - "github.com/openfaas/faas-cli/proxy" "github.com/openfaas/faas-cli/stack" + "github.com/openfaas/faas-cli/version" "github.com/spf13/cobra" ) @@ -24,8 +29,11 @@ var ( sigHeader string key string functionInvokeNamespace string + authenticate bool ) +const functionInvokeRealm = "IAM function invoke" + func init() { // Setup flags that are used by multiple commands (variables defined in faas.go) invokeCmd.Flags().StringVar(&functionName, "name", "", "Name of the deployed function") @@ -36,6 +44,7 @@ func init() { invokeCmd.Flags().StringVar(&contentType, "content-type", "text/plain", "The content-type HTTP header such as application/json") invokeCmd.Flags().StringArrayVar(&query, "query", []string{}, "pass query-string options") invokeCmd.Flags().StringArrayVarP(&headers, "header", "H", []string{}, "pass HTTP request header") + invokeCmd.Flags().BoolVar(&authenticate, "auth", false, "Authenticate with an OpenFaaS token when invoking the function") invokeCmd.Flags().BoolVarP(&invokeAsync, "async", "a", false, "Invoke the function asynchronously") invokeCmd.Flags().StringVarP(&httpMethod, "method", "m", "POST", "pass HTTP request method") invokeCmd.Flags().BoolVar(&tlsInsecure, "tls-no-verify", false, "Disable TLS validation") @@ -60,35 +69,56 @@ var invokeCmd = &cobra.Command{ faas-cli invoke flask --method GET --namespace dev faas-cli invoke env --sign X-GitHub-Event --key yoursecret`, RunE: runInvoke, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(yamlFile) > 0 { + parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter, envsubst) + if err != nil { + return err + } + services = parsedServices + } + + return nil + }, } func runInvoke(cmd *cobra.Command, args []string) error { - var services stack.Services - if len(args) < 1 { return fmt.Errorf("please provide a name for the function") } + functionName = args[0] + + var stackNamespace string + if services != nil { + if function, ok := services.Functions[functionName]; ok { + if len(function.Namespace) > 0 { + stackNamespace = function.Namespace + } + } + } + functionNamespace = getNamespace(functionInvokeNamespace, stackNamespace) if missingSignFlag(sigHeader, key) { return fmt.Errorf("signing requires both --sign and --key ") } - var yamlGateway string - functionName = args[0] + err := validateHTTPMethod(httpMethod) + if err != nil { + return nil + } - if len(yamlFile) > 0 { - parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter, envsubst) - if err != nil { - return err - } + httpHeader, err := parseHeaders(headers) + if err != nil { + return err + } - if parsedServices != nil { - services = *parsedServices - yamlGateway = services.Provider.GatewayURL - } + httpQuery, err := parseQueryValues(query) + if err != nil { + return err } - gatewayAddress := getGatewayURL(gateway, defaultGateway, yamlGateway, os.Getenv(openFaaSURLEnvironment)) + httpHeader.Set("Content-Type", contentType) + httpHeader.Set("User-Agent", fmt.Sprintf("faas-cli/%s (openfaas; %s; %s)", version.BuildVersion(), runtime.GOOS, runtime.GOARCH)) stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) != 0 { @@ -101,38 +131,170 @@ func runInvoke(cmd *cobra.Command, args []string) error { } if len(sigHeader) > 0 { - signedHeader, err := generateSignedHeader(functionInput, key, sigHeader) - if err != nil { - return fmt.Errorf("unable to sign message: %s", err.Error()) - } - headers = append(headers, signedHeader) + sig := generateSignature(functionInput, key) + httpHeader.Add(sigHeader, sig) } - response, err := proxy.InvokeFunction(gatewayAddress, functionName, &functionInput, contentType, query, headers, invokeAsync, httpMethod, tlsInsecure, functionInvokeNamespace) + client, err := GetDefaultSDKClient() if err != nil { return err } - if response != nil { - os.Stdout.Write(*response) + u, _ := url.Parse("/") + u.RawQuery = httpQuery.Encode() + + body := bytes.NewReader(functionInput) + req, err := http.NewRequest(httpMethod, u.String(), body) + if err != nil { + return err } + req.Header = httpHeader - return nil -} + res, err := client.InvokeFunction(functionName, functionNamespace, invokeAsync, authenticate, req) + if err != nil { + return fmt.Errorf("failed to invoke function: %s", err) + } + if res.Body != nil { + defer res.Body.Close() + } + + if !authenticate && res.StatusCode == http.StatusUnauthorized { + authenticateHeader := res.Header.Get("WWW-Authenticate") + realm := getRealm(authenticateHeader) + + // Retry the request and authenticate with an OpenFaaS function access token if the realm directive in the + // WWW-Authenticate header is the function invoke realm. + if realm == functionInvokeRealm { + authenticate := true + body := bytes.NewReader(functionInput) + req, err := http.NewRequest(httpMethod, u.String(), body) + if err != nil { + return err + } + req.Header = httpHeader + + res, err = client.InvokeFunction(functionName, functionInvokeNamespace, invokeAsync, authenticate, req) + if err != nil { + return fmt.Errorf("failed to invoke function: %s", err) + } + if res.Body != nil { + defer res.Body.Close() + } + } + } + + if code := res.StatusCode; code < 200 || code > 299 { + resBody, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("cannot read result from OpenFaaS on URL: %s %s", gateway, err) + } + + return fmt.Errorf("server returned unexpected status code: %d - %s", res.StatusCode, string(resBody)) + } -func generateSignedHeader(message []byte, key string, headerName string) (string, error) { + if invokeAsync && res.StatusCode == http.StatusAccepted { + fmt.Fprintf(os.Stderr, "Function submitted asynchronously.\n") + return nil + } - if len(headerName) == 0 { - return "", fmt.Errorf("signed header must have a non-zero length") + if _, err := io.Copy(os.Stdout, res.Body); err != nil { + return fmt.Errorf("cannot read result from OpenFaaS on URL: %s %s", gateway, err) } + return nil +} + +func generateSignature(message []byte, key string) string { hash := hmac.Sign(message, []byte(key)) signature := hex.EncodeToString(hash) - signedHeader := fmt.Sprintf(`%s=%s=%s`, headerName, "sha1", string(signature[:])) - return signedHeader, nil + return fmt.Sprintf(`%s=%s`, "sha1", string(signature[:])) } func missingSignFlag(header string, key string) bool { return (len(header) > 0 && len(key) == 0) || (len(header) == 0 && len(key) > 0) } + +// parseHeaders parses header values from the header command flag +func parseHeaders(headers []string) (http.Header, error) { + httpHeader := http.Header{} + + for _, header := range headers { + headerVal := strings.SplitN(header, "=", 2) + if len(headerVal) != 2 { + return httpHeader, fmt.Errorf("the --header or -H flag must take the form of key=value") + } + + key, value := headerVal[0], headerVal[1] + if key == "" { + return httpHeader, fmt.Errorf("the --header or -H flag must take the form of key=value (empty key given)") + } + + if value == "" { + return httpHeader, fmt.Errorf("the --header or -H flag must take the form of key=value (empty value given)") + } + + httpHeader.Add(key, value) + } + + return httpHeader, nil +} + +// parseQueryValues parses query values from the query command flags +func parseQueryValues(query []string) (url.Values, error) { + v := url.Values{} + + for _, q := range query { + queryVal := strings.SplitN(q, "=", 2) + if len(queryVal) != 2 { + return v, fmt.Errorf("the --query flag must take the form of key=value") + } + + key, value := queryVal[0], queryVal[1] + if key == "" { + return v, fmt.Errorf("the --header or -H flag must take the form of key=value (empty key given)") + } + + if value == "" { + return v, fmt.Errorf("the --header or -H flag must take the form of key=value (empty value given)") + } + + v.Add(key, value) + } + + return v, nil +} + +// validateMethod validates the HTTP request method +func validateHTTPMethod(httpMethod string) error { + var allowedMethods = []string{ + http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, + } + helpString := strings.Join(allowedMethods, "/") + + if !contains(allowedMethods, httpMethod) { + return fmt.Errorf("the --method or -m flag must take one of these values (%s)", helpString) + } + return nil +} + +// NOTE: This is far from a fully compliant parser per RFC 7235. +// It is only intended to correctly capture the realm directive in the +// known format as returned by the OpenFaaS watchdogs. +func getRealm(headerVal string) string { + parts := strings.SplitN(headerVal, " ", 2) + + realm := "" + if len(parts) > 1 { + directives := strings.Split(parts[1], ", ") + + for _, part := range directives { + if strings.HasPrefix(part, "realm=") { + realm = strings.Trim(part[6:], `"`) + break + } + } + } + + return realm +} diff --git a/commands/invoke_test.go b/commands/invoke_test.go index 4cac702b..d2d3961c 100644 --- a/commands/invoke_test.go +++ b/commands/invoke_test.go @@ -5,14 +5,14 @@ package commands import ( "net/http" + "net/url" "os" + "reflect" "regexp" - "strings" "testing" "io/ioutil" - "github.com/alexellis/hmac" "github.com/openfaas/faas-cli/test" ) @@ -23,14 +23,14 @@ func Test_invoke(t *testing.T) { s := test.MockHttpServer(t, []test.Request{ { Method: http.MethodPost, - Uri: "/function/" + funcName, + Uri: "/function/" + funcName + ".openfaas-fn", ResponseStatusCode: http.StatusOK, ResponseBody: expectedInvokeResponse, }, }) defer s.Close() - os.Stdin, _ = ioutil.TempFile("", "stdin") + os.Stdin, _ = os.CreateTemp("", "stdin") os.Stdin.WriteString("test-data") os.Stdin.Seek(0, 0) defer func() { @@ -58,7 +58,7 @@ func Test_async_invoke(t *testing.T) { s := test.MockHttpServer(t, []test.Request{ { Method: http.MethodPost, - Uri: "/async-function/" + funcName, + Uri: "/async-function/" + funcName + ".openfaas-fn", ResponseStatusCode: http.StatusAccepted, }, }) @@ -87,69 +87,38 @@ func Test_async_invoke(t *testing.T) { } -func Test_generateSignedHeader(t *testing.T) { - - var generateTestcases = []struct { - title string - message []byte - key string - headerName string - expectedSig string - expectedErr bool +func Test_generateHeader(t *testing.T) { + var tests = []struct { + name string + message []byte + key string + want string }{ { - title: "Header with empty key", - message: []byte("This is a message"), - key: "", - headerName: "HeaderSet", - expectedSig: "HeaderSet=sha1=cdefd604e685e5c8b31fbcf6621a6e8282770dfe", - expectedErr: false, - }, - { - title: "Key with empty Header", - message: []byte("This is a message"), - key: "KeySet", - headerName: "", - expectedSig: "", - expectedErr: true, + name: "Empty key", + message: []byte("This is a message"), + key: "", + want: "sha1=cdefd604e685e5c8b31fbcf6621a6e8282770dfe", }, { - title: "Header & key with empty message", - message: []byte(""), - key: "KeySet", - headerName: "HeaderSet", - expectedSig: "HeaderSet=sha1=33dcd94ffaf13fce58615585c030c1a39d100b3c", - expectedErr: false, + name: "Key with empty message", + message: []byte(""), + key: "KeySet", + want: "sha1=33dcd94ffaf13fce58615585c030c1a39d100b3c", }, { - title: "Header with empty message & key", - message: []byte(""), - key: "", - headerName: "HeaderSet", - expectedSig: "HeaderSet=sha1=fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", - expectedErr: false, + name: "Empty key and message", + message: []byte(""), + key: "", + want: "sha1=fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", }, } - for _, test := range generateTestcases { - t.Run(test.title, func(t *testing.T) { - sig, err := generateSignedHeader(test.message, test.key, test.headerName) - - if sig != test.expectedSig { - t.Fatalf("error generating signature, wanted: %s, got %s", test.expectedSig, sig) - } - if (err != nil) != test.expectedErr { - t.Fatalf("error generating expected error: %v, got: %v", err != nil, test.expectedErr) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := generateSignature(test.message, test.key) - if test.expectedErr == false { - - encodedHash := strings.SplitN(test.expectedSig, "=", 2) - - invalid := hmac.Validate(test.message, encodedHash[1], test.key) - - if invalid != nil { - t.Fatalf("expected no error, but got: %s", invalid.Error()) - } + if got != test.want { + t.Fatalf("error generating signature, want: %s, got %s", test.want, got) } }) } @@ -199,3 +168,180 @@ func Test_missingSignFlag(t *testing.T) { }) } } + +func Test_parseHeaders_valid(t *testing.T) { + tests := []struct { + name string + input []string + want http.Header + }{ + { + name: "Header with key-value pair as value", + input: []string{`X-Hub-Signature="sha1="shashashaebaf43""`, "X-Hub-Signature-1=sha1=shashashaebaf43"}, + want: http.Header{ + "X-Hub-Signature": []string{`"sha1="shashashaebaf43""`}, + "X-Hub-Signature-1": []string{"sha1=shashashaebaf43"}, + }, + }, + { + name: "Header with normal values", + input: []string{`X-Hub-Signature="shashashaebaf43"`, "X-Hub-Signature-1=shashashaebaf43"}, + want: http.Header{"X-Hub-Signature": []string{`"shashashaebaf43"`}, "X-Hub-Signature-1": []string{"shashashaebaf43"}}, + }, + { + name: "Header with base64 string value", + input: []string{`X-Hub-Signature="shashashaebaf43="`}, + want: http.Header{"X-Hub-Signature": []string{`"shashashaebaf43="`}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := parseHeaders(test.input) + + if err != nil { + t.Fatalf("want: %s, got error: %s", test.want, err) + } + + if err == nil && !reflect.DeepEqual(test.want, got) { + t.Fatalf("want: %s, got: %s", test.want, got) + } + }) + } +} + +func Test_parseHeaders_invalid(t *testing.T) { + tests := []struct { + name string + input []string + }{ + { + name: "Invalid header string", + input: []string{"invalid_header"}, + }, + { + name: "Empty header key", + input: []string{"=shashashaebaf43"}, + }, + { + name: "Empty header value", + input: []string{"X-Hub-Signature="}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := parseHeaders(test.input) + + if err == nil { + t.Fatalf("want err, got nil") + } + }) + } +} + +func Test_parseQueryValues_valid(t *testing.T) { + tests := []struct { + name string + input []string + want url.Values + }{ + { + name: "Header with key-value pair as value", + input: []string{`key1="nkey1="nval1""`, "key2=nkey2=nval2"}, + want: url.Values{ + "key1": []string{`"nkey1="nval1""`}, + "key2": []string{"nkey2=nval2"}, + }, + }, + { + name: "Header with normal values", + input: []string{`key1="val1"`, "key2=val2"}, + want: url.Values{ + "key1": []string{`"val1"`}, + "key2": []string{"val2"}, + }, + }, + { + name: "Header with base64 string value", + input: []string{`key="shashashaebaf43="`}, + want: url.Values{"key": []string{`"shashashaebaf43="`}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := parseQueryValues(test.input) + + if err != nil { + t.Fatalf("want: %s, got error: %s", test.want, err) + } + + if err == nil && !reflect.DeepEqual(test.want, got) { + t.Fatalf("want: %s, got: %s", test.want, got) + } + }) + } +} + +func Test_parseQueryValues_invalid(t *testing.T) { + tests := []struct { + name string + input []string + }{ + { + name: "Invalid query value string", + input: []string{"invalid"}, + }, + { + name: "Empty query key", + input: []string{"=bar"}, + }, + { + name: "Empty query value", + input: []string{"foo="}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := parseQueryValues(test.input) + + if err == nil { + t.Fatalf("want err, got nil") + } + }) + } +} + +func Test_getRealm(t *testing.T) { + tests := []struct { + header string + want string + }{ + { + header: "Bearer", + want: "", + }, + { + header: `Bearer realm="OpenFaaS API"`, + want: "OpenFaaS API", + }, + { + header: `Bearer realm="OpenFaaS API", charset="UTF-8"`, + want: "OpenFaaS API", + }, + { + header: "", + want: "", + }, + } + + for _, test := range tests { + got := getRealm(test.header) + + if test.want != got { + t.Errorf("want: %s, got: %s", test.want, got) + } + } +} diff --git a/commands/local_run.go b/commands/local_run.go index 024636d7..6cdf2a9e 100644 --- a/commands/local_run.go +++ b/commands/local_run.go @@ -195,7 +195,21 @@ func runFunction(ctx context.Context, name string, opts runOptions, args []strin // Always try to remove before running, to clear up any previous state removeContainer(name) - cmd, err := buildDockerRun(ctx, name, services.Functions[name], opts) + function := services.Functions[name] + + functionNamespace = function.Namespace + if len(functionNamespace) == 0 { + functionNamespace = "openfaas-fn" + } + + // Add openfaas env variables that are normally injected by the provider. + opts.extraEnv["OPENFAAS_NAME"] = name + opts.extraEnv["OPENFAAS_NAMESPACE"] = functionNamespace + + // Enable local jwt auth by default + opts.extraEnv["jwt_auth_local"] = "true" + + cmd, err := buildDockerRun(ctx, name, function, opts) if err != nil { return err } diff --git a/go.mod b/go.mod index 65283655..a3cab6d4 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/openfaas/faas-cli go 1.22 +replace github.com/openfaas/go-sdk => /home/welteki/code/openfaas/oss/go-sdk + require ( github.com/Masterminds/semver v1.5.0 github.com/alexellis/arkade v0.0.0-20240607165001-5fb6c68bd5a7 @@ -32,47 +34,28 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/VividCortex/ewma v1.2.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheggaaa/pb/v3 v3.1.5 // indirect github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/docker/cli v24.0.7+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.17.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/gorilla/mux v1.8.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/nats-io/nats.go v1.35.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/nats-io/stan.go v0.10.4 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24 // indirect - github.com/otiai10/copy v1.14.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.53.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect - github.com/sethvargo/go-password v0.3.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/vbatts/tar-split v0.11.5 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.21.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 9b88955e..ecf4b8cd 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/alexellis/arkade v0.0.0-20231127111019-8363b67093b1 h1:qV8L25IL6PwuxjsiNYaU1Ai+DJ/sXVK3C+6rH3IWaTQ= -github.com/alexellis/arkade v0.0.0-20231127111019-8363b67093b1/go.mod h1:npFKLD/AkLBdDgTna28LaF2UfaiihJYnIp1mNaALyS4= github.com/alexellis/arkade v0.0.0-20240607165001-5fb6c68bd5a7 h1:7dX+tV/ouqT7NrLdFEbaDcDMTbZYX61EytmUYmRm40M= github.com/alexellis/arkade v0.0.0-20240607165001-5fb6c68bd5a7/go.mod h1:bSpf4IFjFUXqwW3F54kImsDpTkFv0LbCMpuqahHrHZQ= github.com/alexellis/go-execute/v2 v2.2.1 h1:4Ye3jiCKQarstODOEmqDSRCqxMHLkC92Bhse743RdOI= @@ -14,45 +12,28 @@ github.com/alexellis/hmac v1.3.0 h1:DJl5wfuhwj2IjG9XRXzPY6bHZYrwrARFTotpxX3KS08= github.com/alexellis/hmac v1.3.0/go.mod h1:WmZwlIfB7EQaDuiScnQoMSs3K+1UalW/7ExXP3Cc2zU= github.com/alexellis/hmac/v2 v2.0.0 h1:/sH/UJxDXPpJorUeg2DudeKSeUrWPF32Yamw2TiDoOQ= github.com/alexellis/hmac/v2 v2.0.0/go.mod h1:O7hZZgTfh5fp5+vAamzodZPlbw+aQK+nnrrJNHsEvL0= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= -github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk= github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= -github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= -github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -63,36 +44,24 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ= -github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -106,61 +75,28 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA= -github.com/nats-io/nats.go v1.35.0 h1:XFNqNM7v5B+MQMKqVGAyHwYhyKb48jrenXNxIU20ULk= -github.com/nats-io/nats.go v1.35.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nats-io/stan.go v0.10.4 h1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw= -github.com/nats-io/stan.go v0.10.4/go.mod h1:3XJXH8GagrGqajoO/9+HgPyKV5MWsv7S5ccdda+pc6k= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/openfaas/faas-provider v0.24.4 h1:Zzbkabgd0PoQmnRjy53NbMXjhLaIyoIiwP3qaLkm9rE= -github.com/openfaas/faas-provider v0.24.4/go.mod h1:NsETIfEndZn4mn/w/XnBTcDTwKqULCziphLp7KgeRcA= github.com/openfaas/faas-provider v0.25.3 h1:cy5GKP1R/xZkPjg+9We7yqpfz298GrKw4ZRYJVprt7Q= github.com/openfaas/faas-provider v0.25.3/go.mod h1:NsETIfEndZn4mn/w/XnBTcDTwKqULCziphLp7KgeRcA= -github.com/openfaas/faas/gateway v0.0.0-20231102155424-02205b8b1954 h1:IzwqiRtczzZp2k2TI/hgvl5ONIihEw4CTlj4jFCDaPE= -github.com/openfaas/faas/gateway v0.0.0-20231102155424-02205b8b1954/go.mod h1:8gQ9xS+e9dCS5uxhc/HSiNdchosVT5U1kTvgStPWSF0= github.com/openfaas/faas/gateway v0.0.0-20240531125512-3d2808354de4 h1:7anaUupp2YojXNZRX9e1heLDw03oqpHA1e0VjGW6f8c= github.com/openfaas/faas/gateway v0.0.0-20240531125512-3d2808354de4/go.mod h1:H2XTI32KNVQfFd1sl9w6eMV25r/Zdc/MJ6ATGCR8Nl0= -github.com/openfaas/go-sdk v0.2.8 h1:6ZatLMoogWZHlaCpPZi6e0z/F9P6z30jl9clhA908jc= -github.com/openfaas/go-sdk v0.2.8/go.mod h1:f7G4OREhHcW3kwwqRvDZR7VYCta/N7ULmTvngy9f6pI= -github.com/openfaas/go-sdk v0.2.10 h1:Kyul1YMND8WC7S0RowEfIPSAGSCHeBHEB36y6TZuFEo= -github.com/openfaas/go-sdk v0.2.10/go.mod h1:rAUO7ww67oVhacxAQkvSJPzYNJGWas31gCFkZZVnZrE= -github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24 h1:IeFWHV+vpviPvmAVrh6SsW19PkU+7+Pu/CLrFIe1gtc= -github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24/go.mod h1:SR1bzVXQaZoZS+wWmvO7bXZE6V9S9bukR9J5Kynr/vc= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= -github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sethvargo/go-password v0.3.0 h1:OLFHZ91Z7NiNP3dnaPxLxCDXlb6TBuxFzMvv6bu+Ptw= -github.com/sethvargo/go-password v0.3.0/go.mod h1:p6we8DZ0eyYXof9pon7Cqrw98N4KTaYiadDml1dUEEw= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= @@ -169,84 +105,29 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/vendor/github.com/openfaas/go-sdk/README.md b/vendor/github.com/openfaas/go-sdk/README.md index 9ef89847..3f2ce3b2 100644 --- a/vendor/github.com/openfaas/go-sdk/README.md +++ b/vendor/github.com/openfaas/go-sdk/README.md @@ -143,6 +143,77 @@ if err != nil { Please refer [examples](https://github.com/openfaas/go-sdk/tree/master/examples) folder for code examples of each operation +## Invoke functions + +```go +body := strings.NewReader("OpenFaaS") +req, err := http.NewRequestWithContext(context.TODO(), http.MethodPost, "/", body) +if err != nil { + panic(err) +} + +req.Header.Set("Content-Type", "text/plain") + +async := false +authenticate := false + +// Make a POST request to a figlet function in the openfaas-fn namespace +res, err := client.InvokeFunction(context.Background(), "figlet", "openfaas-fn", async, authenticate, req) +if err != nil { + log.Printf("Failed to invoke function: %s", err) + return +} + +if res.Body != nil { + defer res.Body.Close() +} + +// Read the response body +body, err := io.ReadAll(res.Body) +if err != nil { + log.Printf("Error reading response body: %s", err) + return +} + +// Print the response +fmt.Printf("Response status code: %s\n", res.Status) +fmt.Printf("Response body: %s\n", string(body)) +``` + +### Authenticate function invocations + +The SDK supports invoking functions if you are using OpenFaaS IAM with [built-in authentication for functions](https://www.openfaas.com/blog/built-in-function-authentication/). + +Set the `auth` argument to `true` when calling `InvokeFunction` to authenticate the request with an OpenFaaS function access token. + +The `Client` needs a `TokenSource` to get an ID token that can be exchanged for a function access token to make authenticated function invocations. By default the `TokenAuth` provider that was set when constructing a new `Client` is used. + +It is also possible to provide a custom `TokenSource` for the function token exchange: + +```go +ts := sdk.NewClientCredentialsTokenSource(clientID, clientSecret, tokenURL, scope, grantType, audience) + +client := sdk.NewClientWithOpts(gatewayURL, http.DefaultClient, sdk.WithFunctionTokenSource(ts)) +``` + +Optionally a `TokenCache` can be configured to cache function access tokens and prevent the client from having to do a token exchange each time a function is invoked. + +```go +ctx, cancel := context.WithCancel(context.Background()) +defer cancel() + +fnTokenCache := sdk.NewMemoryTokenCache() +// Start garbage collection to remove expired tokens from the cache. +go fnTokenCache.StartGC(ctx, time.Second*10) + +client := sdk.NewClientWithOpts( + gatewayUrl, + httpClient, + sdk.WithAuthentication(auth), + sdk.WithFunctionTokenCache(fnTokenCache), +) +``` + ## License License: MIT diff --git a/vendor/github.com/openfaas/go-sdk/client.go b/vendor/github.com/openfaas/go-sdk/client.go index ef21fa2f..9ca378f6 100644 --- a/vendor/github.com/openfaas/go-sdk/client.go +++ b/vendor/github.com/openfaas/go-sdk/client.go @@ -19,49 +19,34 @@ import ( "github.com/openfaas/faas-provider/types" ) -// Client is used to manage OpenFaaS functions +// Client is used to manage OpenFaaS and invoke functions type Client struct { + // URL of the OpenFaaS gateway GatewayURL *url.URL - client *http.Client + + // Authentication provider for authenticating request to the OpenFaaS API. ClientAuth ClientAuth + + // TokenSource for getting an ID token that can be exchanged for an + // OpenFaaS function access token to invoke functions. + FunctionTokenSource TokenSource + + // Http client used for calls to the OpenFaaS gateway. + client *http.Client + + // OpenFaaS function access token cache for invoking functions. + fnTokenCache TokenCache } // Wrap http request Do function to support debug capabilities func (s *Client) do(req *http.Request) (*http.Response, error) { if os.Getenv("FAAS_DEBUG") == "1" { - - fmt.Printf("%s %s\n", req.Method, req.URL.String()) - for k, v := range req.Header { - if k == "Authorization" { - auth := "[REDACTED]" - if len(v) == 0 { - auth = "[NOT_SET]" - } else { - l, _, ok := strings.Cut(v[0], " ") - if ok && (l == "Basic" || l == "Bearer") { - auth = l + " REDACTED" - } - } - fmt.Printf("%s: %s\n", k, auth) - - } else { - fmt.Printf("%s: %s\n", k, v) - } + dump, err := dumpRequest(req) + if err != nil { + return nil, err } - if req.Body != nil { - r := io.NopCloser(req.Body) - buf := new(strings.Builder) - _, err := io.Copy(buf, r) - if err != nil { - return nil, err - } - bodyDebug := buf.String() - if len(bodyDebug) > 0 { - fmt.Printf("%s\n", bodyDebug) - } - req.Body = io.NopCloser(strings.NewReader(buf.String())) - } + fmt.Println(dump) } return s.client.Do(req) @@ -73,13 +58,58 @@ type ClientAuth interface { Set(req *http.Request) error } -// NewClient creates an Client for managing OpenFaaS +type ClientOption func(*Client) + +// WithFunctionTokenSource configures the function token source for the client. +func WithFunctionTokenSource(tokenSource TokenSource) ClientOption { + return func(c *Client) { + c.FunctionTokenSource = tokenSource + } +} + +// WithAuthentication configures the authentication provider fot the client. +func WithAuthentication(auth ClientAuth) ClientOption { + return func(c *Client) { + c.ClientAuth = auth + } +} + +// WithFunctionTokenCache configures the token cache used by the client to cache access +// tokens for function invocations. +func WithFunctionTokenCache(cache TokenCache) ClientOption { + return func(c *Client) { + c.fnTokenCache = cache + } +} + +// NewClient creates a Client for managing OpenFaaS and invoking functions func NewClient(gatewayURL *url.URL, auth ClientAuth, client *http.Client) *Client { - return &Client{ + return NewClientWithOpts(gatewayURL, client, WithAuthentication(auth)) +} + +// NewClientWithOpts creates a Client for managing OpenFaaS and invoking functions +// It takes a list of ClientOptions to configure the client. +func NewClientWithOpts(gatewayURL *url.URL, client *http.Client, options ...ClientOption) *Client { + c := &Client{ GatewayURL: gatewayURL, - client: client, - ClientAuth: auth, + + client: client, + } + + for _, option := range options { + option(c) + } + + if c.ClientAuth != nil && c.FunctionTokenSource == nil { + // Use auth as the default function token source for IAM function authentication + // if it implements the TokenSource interface. + functionTokenSource, ok := c.ClientAuth.(TokenSource) + if ok { + c.FunctionTokenSource = functionTokenSource + } } + + return c } // GetNamespaces get openfaas namespaces @@ -935,3 +965,43 @@ func (s *Client) GetLogs(ctx context.Context, functionName, namespace string, fo } return logStream, nil } + +func dumpRequest(req *http.Request) (string, error) { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf("%s %s\n", req.Method, req.URL.String())) + for k, v := range req.Header { + if k == "Authorization" { + auth := "[REDACTED]" + if len(v) == 0 { + auth = "[NOT_SET]" + } else { + l, _, ok := strings.Cut(v[0], " ") + if ok && (l == "Basic" || l == "Bearer") { + auth = l + " [REDACTED]" + } + } + sb.WriteString(fmt.Sprintf("%s: %s\n", k, auth)) + + } else { + sb.WriteString(fmt.Sprintf("%s: %s\n", k, v)) + } + } + + if req.Body != nil { + r := io.NopCloser(req.Body) + buf := new(strings.Builder) + _, err := io.Copy(buf, r) + if err != nil { + return "", err + } + bodyDebug := buf.String() + if len(bodyDebug) > 0 { + sb.WriteString(fmt.Sprintf("%s\n", bodyDebug)) + + } + req.Body = io.NopCloser(strings.NewReader(buf.String())) + } + + return sb.String(), nil +} diff --git a/vendor/github.com/openfaas/go-sdk/client_credentials_auth.go b/vendor/github.com/openfaas/go-sdk/client_credentials_auth.go index 0696eea4..30265046 100644 --- a/vendor/github.com/openfaas/go-sdk/client_credentials_auth.go +++ b/vendor/github.com/openfaas/go-sdk/client_credentials_auth.go @@ -1,14 +1,13 @@ package sdk import ( - "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" + "strings" "sync" - "time" ) type ClientCredentialsAuth struct { @@ -41,7 +40,7 @@ type ClientCredentialsTokenSource struct { scope string grantType string audience string - token *ClientCredentialsToken + token *Token lock sync.RWMutex } @@ -72,28 +71,26 @@ func (ts *ClientCredentialsTokenSource) Token() (string, error) { ts.token = token ts.lock.Unlock() - return token.AccessToken, nil + return ts.token.IDToken, nil } ts.lock.RUnlock() - return ts.token.AccessToken, nil + return ts.token.IDToken, nil } -func obtainClientCredentialsToken(clientID, clientSecret, tokenURL, scope, grantType, audience string) (*ClientCredentialsToken, error) { +func obtainClientCredentialsToken(clientID, clientSecret, tokenURL, scope, grantType, audience string) (*Token, error) { - reqBody := url.Values{} - reqBody.Set("client_id", clientID) - reqBody.Set("client_secret", clientSecret) - reqBody.Set("grant_type", grantType) - reqBody.Set("scope", scope) + v := url.Values{} + v.Set("client_id", clientID) + v.Set("client_secret", clientSecret) + v.Set("grant_type", grantType) + v.Set("scope", scope) if len(audience) > 0 { - reqBody.Set("audience", audience) + v.Set("audience", audience) } - buffer := []byte(reqBody.Encode()) - - req, err := http.NewRequest(http.MethodPost, tokenURL, bytes.NewBuffer(buffer)) + req, err := http.NewRequest(http.MethodPost, tokenURL, strings.NewReader(v.Encode())) if err != nil { return nil, err } @@ -104,45 +101,26 @@ func obtainClientCredentialsToken(clientID, clientSecret, tokenURL, scope, grant return nil, err } - var body []byte if res.Body != nil { defer res.Body.Close() - body, _ = io.ReadAll(res.Body) } - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code %d, body: %s", res.StatusCode, string(body)) + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("cannot fetch token: %v", err) } - token := &ClientCredentialsToken{} - if err := json.Unmarshal(body, token); err != nil { - return nil, fmt.Errorf("unable to unmarshal token: %s", err) + if code := res.StatusCode; code < 200 || code > 299 { + return nil, fmt.Errorf("unexpected status code: %v\nResponse: %s", res.Status, body) } - token.ObtainedAt = time.Now() - - return token, nil -} - -// ClientCredentialsToken represents an access_token -// obtained through the client credentials grant type. -// This token is not associated with a human user. -type ClientCredentialsToken struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - ObtainedAt time.Time -} - -// Expired returns true if the token is expired -// or if the expiry time is not known. -// The token will always expire 10s early to avoid -// clock skew. -func (t *ClientCredentialsToken) Expired() bool { - if t.ExpiresIn == 0 { - return false + tj := &tokenJSON{} + if err := json.Unmarshal(body, tj); err != nil { + return nil, fmt.Errorf("unable to unmarshal token: %s", err) } - expiry := t.ObtainedAt.Add(time.Duration(t.ExpiresIn) * time.Second).Add(-expiryDelta) - return expiry.Before(time.Now()) + return &Token{ + IDToken: tj.AccessToken, + Expiry: tj.expiry(), + }, nil } diff --git a/vendor/github.com/openfaas/go-sdk/exchange.go b/vendor/github.com/openfaas/go-sdk/exchange.go index db13ff27..36c37f6c 100644 --- a/vendor/github.com/openfaas/go-sdk/exchange.go +++ b/vendor/github.com/openfaas/go-sdk/exchange.go @@ -6,18 +6,35 @@ import ( "io" "net/http" "net/url" + "os" "strings" ) // Exchange an OIDC ID Token from an IdP for OpenFaaS token // using the token exchange grant type. // tokenURL should be the OpenFaaS token endpoint within the internal OIDC service -func ExchangeIDToken(tokenURL, rawIDToken string) (*Token, error) { +func ExchangeIDToken(tokenURL, rawIDToken string, options ...ExchangeOption) (*Token, error) { + c := &ExchangeConfig{ + Client: http.DefaultClient, + } + + for _, option := range options { + option(c) + } + v := url.Values{} v.Set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange") v.Set("subject_token_type", "urn:ietf:params:oauth:token-type:id_token") v.Set("subject_token", rawIDToken) + for _, aud := range c.Audience { + v.Add("audience", aud) + } + + if len(c.Scope) > 0 { + v.Set("scope", strings.Join(c.Scope, " ")) + } + u, _ := url.Parse(tokenURL) req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(v.Encode())) @@ -27,7 +44,16 @@ func ExchangeIDToken(tokenURL, rawIDToken string) (*Token, error) { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - res, err := http.DefaultClient.Do(req) + if os.Getenv("FAAS_DEBUG") == "1" { + dump, err := dumpRequest(req) + if err != nil { + return nil, err + } + + fmt.Println(dump) + } + + res, err := c.Client.Do(req) if err != nil { return nil, err } @@ -41,8 +67,15 @@ func ExchangeIDToken(tokenURL, rawIDToken string) (*Token, error) { return nil, fmt.Errorf("cannot fetch token: %v", err) } + if res.StatusCode == http.StatusBadRequest { + authErr := &OAuthError{} + if err := json.Unmarshal(body, authErr); err == nil { + return nil, authErr + } + } + if code := res.StatusCode; code < 200 || code > 299 { - return nil, fmt.Errorf("cannot fetch token: %v\nResponse: %s", res.Status, body) + return nil, fmt.Errorf("unexpected status code: %v\nResponse: %s", res.Status, body) } tj := &tokenJSON{} @@ -53,5 +86,40 @@ func ExchangeIDToken(tokenURL, rawIDToken string) (*Token, error) { return &Token{ IDToken: tj.AccessToken, Expiry: tj.expiry(), + Scope: tj.scope(), }, nil } + +type ExchangeConfig struct { + Audience []string + Scope []string + Client *http.Client +} + +// ExchangeOption is used to implement functional-style options that modify the +// config setting for the OpenFaaS token exchange. +type ExchangeOption func(*ExchangeConfig) + +// WithAudience is an option to configure the audience requested +// in the token exchange. +func WithAudience(audience []string) ExchangeOption { + return func(c *ExchangeConfig) { + c.Audience = audience + } +} + +// WithScope is an option to configure the scope requested +// in the token exchange. +func WithScope(scope []string) ExchangeOption { + return func(c *ExchangeConfig) { + c.Scope = scope + } +} + +// WithHttpClient is an option to configure the http client +// used to make the token exchange request. +func WithHttpClient(client *http.Client) ExchangeOption { + return func(c *ExchangeConfig) { + c.Client = client + } +} diff --git a/vendor/github.com/openfaas/go-sdk/functions.go b/vendor/github.com/openfaas/go-sdk/functions.go new file mode 100644 index 00000000..ae14c9ec --- /dev/null +++ b/vendor/github.com/openfaas/go-sdk/functions.go @@ -0,0 +1,63 @@ +package sdk + +import ( + "fmt" + "net/http" +) + +const DefaultNamespace = "openfaas-fn" + +func (c *Client) InvokeFunction(name, namespace string, async bool, auth bool, req *http.Request) (*http.Response, error) { + fnEndpoint := "/function" + if async { + fnEndpoint = "/async-function" + } + + if len(namespace) == 0 { + namespace = DefaultNamespace + } + + req.URL.Scheme = c.GatewayURL.Scheme + req.URL.Host = c.GatewayURL.Host + req.URL.Path = fmt.Sprintf("%s/%s.%s", fnEndpoint, name, namespace) + + if auth && c.FunctionTokenSource != nil { + idToken, err := c.FunctionTokenSource.Token() + if err != nil { + return nil, fmt.Errorf("failed to get function access token: %w", err) + } + + tokenURL := fmt.Sprintf("%s/oauth/token", c.GatewayURL.String()) + scope := []string{"function"} + audience := []string{fmt.Sprintf("%s:%s", namespace, name)} + + var bearer string + if c.fnTokenCache != nil { + // Function access tokens are cached as long as the token is valid + // to prevent having to do a token exchange each time the function is invoked. + cacheKey := fmt.Sprintf("%s.%s", name, namespace) + token, ok := c.fnTokenCache.Get(cacheKey) + if !ok { + token, err = ExchangeIDToken(tokenURL, idToken, WithScope(scope), WithAudience(audience)) + if err != nil { + return nil, fmt.Errorf("failed to get function access token: %w", err) + } + + c.fnTokenCache.Set(cacheKey, token) + } + + bearer = token.IDToken + } else { + token, err := ExchangeIDToken(tokenURL, idToken, WithScope(scope), WithAudience(audience)) + if err != nil { + return nil, fmt.Errorf("failed to get function access token: %w", err) + } + + bearer = token.IDToken + } + + req.Header.Add("Authorization", "Bearer "+bearer) + } + + return c.do(req) +} diff --git a/vendor/github.com/openfaas/go-sdk/iam_auth.go b/vendor/github.com/openfaas/go-sdk/iam_auth.go index e672beda..8760334c 100644 --- a/vendor/github.com/openfaas/go-sdk/iam_auth.go +++ b/vendor/github.com/openfaas/go-sdk/iam_auth.go @@ -1,6 +1,7 @@ package sdk import ( + "errors" "fmt" "net/http" "os" @@ -32,24 +33,43 @@ type TokenAuth struct { // Set validates the token expiry on each call. If it's expired it will exchange // an ID token from the TokenSource for a new OpenFaaS token. func (a *TokenAuth) Set(req *http.Request) error { + token, err := a.getToken() + if err != nil { + return err + } + + req.Header.Add("Authorization", "Bearer "+token) + return nil +} + +func (a *TokenAuth) Token() (string, error) { + return a.getToken() +} + +func (a *TokenAuth) getToken() (string, error) { a.lock.Lock() defer a.lock.Unlock() if a.token == nil || a.token.Expired() { idToken, err := a.TokenSource.Token() if err != nil { - return err + return "", err } token, err := ExchangeIDToken(a.TokenURL, idToken) + + var authError *OAuthError + if errors.As(err, &authError) { + return "", fmt.Errorf("failed to exchange token for an OpenFaaS token: %s", authError.Description) + } if err != nil { - return err + return "", fmt.Errorf("failed to exchange token for an OpenFaaS token: %s", err) } + a.token = token } - req.Header.Add("Authorization", "Bearer "+a.token.IDToken) - return nil + return a.token.IDToken, nil } // A TokenSource to get ID token by reading a Kubernetes projected service account token diff --git a/vendor/github.com/openfaas/go-sdk/token.go b/vendor/github.com/openfaas/go-sdk/token.go index 981fc534..2b48a73a 100644 --- a/vendor/github.com/openfaas/go-sdk/token.go +++ b/vendor/github.com/openfaas/go-sdk/token.go @@ -1,6 +1,8 @@ package sdk import ( + "fmt" + "strings" "time" ) @@ -9,7 +11,7 @@ import ( // expirations due to client-server time mismatches. const expiryDelta = 10 * time.Second -// Token represents an OpenFaaS ID token +// Token represents an OIDC token type Token struct { // IDToken is the OIDC access token that authorizes and authenticates // the requests. @@ -19,6 +21,9 @@ type Token struct { // // A zero value means the token never expires. Expiry time.Time + + // Scope is the scope of the access token + Scope []string } // Expired reports whether the token is expired, and will start @@ -31,13 +36,13 @@ func (t *Token) Expired() bool { return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now()) } +// tokenJson represents an OAuth token response type tokenJSON struct { AccessToken string `json:"access_token"` - IDToken string `json:"id_token"` - - TokenType string `json:"token_type"` + TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` + ExpiresIn int `json:"expires_in"` + Scope string `json:"scope"` } func (t *tokenJSON) expiry() (exp time.Time) { @@ -46,3 +51,24 @@ func (t *tokenJSON) expiry() (exp time.Time) { } return } + +func (t *tokenJSON) scope() []string { + if len(t.Scope) > 0 { + return strings.Split(t.Scope, " ") + } + + return []string{} +} + +// OAuthError represents an OAuth error response. +type OAuthError struct { + Err string `json:"error"` + Description string `json:"error_description,omitempty"` +} + +func (e *OAuthError) Error() string { + if len(e.Description) > 0 { + return fmt.Sprintf("%s: %s", e.Err, e.Description) + } + return e.Err +} diff --git a/vendor/github.com/openfaas/go-sdk/tokencache.go b/vendor/github.com/openfaas/go-sdk/tokencache.go new file mode 100644 index 00000000..99352454 --- /dev/null +++ b/vendor/github.com/openfaas/go-sdk/tokencache.go @@ -0,0 +1,82 @@ +package sdk + +import ( + "context" + "sync" + "time" +) + +type TokenCache interface { + Get(key string) (*Token, bool) + Set(key string, token *Token) +} + +// MemoryTokenCache is a basic in-memory token cache implementation. +type MemoryTokenCache struct { + tokens map[string]*Token + + lock sync.RWMutex +} + +// NewMemoryTokenCache creates a new in memory token cache instance. +func NewMemoryTokenCache() *MemoryTokenCache { + return &MemoryTokenCache{ + tokens: map[string]*Token{}, + } +} + +// Set adds or updates a token with the given key in the cache. +func (c *MemoryTokenCache) Set(key string, token *Token) { + c.lock.Lock() + defer c.lock.Unlock() + + c.tokens[key] = token +} + +// Get retrieves the token associated with the given key from the cache. The bool +// return value will be false if no matching key is found, and true otherwise. +func (c *MemoryTokenCache) Get(key string) (*Token, bool) { + c.lock.RLock() + token, ok := c.tokens[key] + c.lock.RUnlock() + + if ok && token.Expired() { + c.lock.Lock() + delete(c.tokens, key) + c.lock.Unlock() + + return nil, false + } + + return token, ok +} + +// StartGC starts garbage collection of expired tokens. +func (c *MemoryTokenCache) StartGC(ctx context.Context, gcInterval time.Duration) { + if gcInterval <= 0 { + return + } + + ticker := time.NewTicker(gcInterval) + + for { + select { + case <-ticker.C: + c.clearExpired() + case <-ctx.Done(): + ticker.Stop() + return + } + } +} + +// clearExpired removes all expired tokens from the cache. +func (c *MemoryTokenCache) clearExpired() { + for key, token := range c.tokens { + if token.Expired() { + c.lock.Lock() + delete(c.tokens, key) + c.lock.Unlock() + } + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8bb5fc87..0387b9b8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,13 +24,9 @@ github.com/alexellis/hmac # github.com/alexellis/hmac/v2 v2.0.0 ## explicit; go 1.16 github.com/alexellis/hmac/v2 -# github.com/beorn7/perks v1.0.1 -## explicit; go 1.11 # github.com/bep/debounce v1.2.1 ## explicit github.com/bep/debounce -# github.com/cespare/xxhash/v2 v2.3.0 -## explicit; go 1.11 # github.com/cheggaaa/pb/v3 v3.1.5 ## explicit; go 1.17 github.com/cheggaaa/pb/v3 @@ -55,8 +51,6 @@ github.com/docker/docker/pkg/homedir ## explicit; go 1.19 github.com/docker/docker-credential-helpers/client github.com/docker/docker-credential-helpers/credentials -# github.com/docker/go-units v0.5.0 -## explicit # github.com/drone/envsubst v1.0.3 ## explicit; go 1.13 github.com/drone/envsubst @@ -83,8 +77,6 @@ github.com/go-git/go-git/v5/internal/path_util github.com/go-git/go-git/v5/plumbing/format/config github.com/go-git/go-git/v5/plumbing/format/gitignore github.com/go-git/go-git/v5/utils/ioutil -# github.com/gogo/protobuf v1.3.2 -## explicit; go 1.15 # github.com/google/go-cmp v0.6.0 ## explicit; go 1.13 github.com/google/go-cmp/cmp @@ -122,8 +114,6 @@ github.com/google/go-containerregistry/pkg/v1/remote/transport github.com/google/go-containerregistry/pkg/v1/stream github.com/google/go-containerregistry/pkg/v1/tarball github.com/google/go-containerregistry/pkg/v1/types -# github.com/gorilla/mux v1.8.1 -## explicit; go 1.20 # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap @@ -161,14 +151,6 @@ github.com/moby/term/windows # github.com/morikuni/aec v1.0.0 ## explicit github.com/morikuni/aec -# github.com/nats-io/nats.go v1.35.0 -## explicit; go 1.20 -# github.com/nats-io/nkeys v0.4.7 -## explicit; go 1.20 -# github.com/nats-io/nuid v1.0.1 -## explicit -# github.com/nats-io/stan.go v0.10.4 -## explicit; go 1.14 # github.com/olekukonko/tablewriter v0.0.5 ## explicit; go 1.12 github.com/olekukonko/tablewriter @@ -187,32 +169,18 @@ github.com/openfaas/faas-provider/types # github.com/openfaas/faas/gateway v0.0.0-20240531125512-3d2808354de4 ## explicit; go 1.22 github.com/openfaas/faas/gateway/types -# github.com/openfaas/go-sdk v0.2.10 +# github.com/openfaas/go-sdk v0.2.10 => /home/welteki/code/openfaas/oss/go-sdk ## explicit; go 1.21 github.com/openfaas/go-sdk -# github.com/openfaas/nats-queue-worker v0.0.0-20231219105451-b94918cb8a24 -## explicit; go 1.20 -# github.com/otiai10/copy v1.14.0 -## explicit; go 1.18 # github.com/pkg/errors v0.9.1 ## explicit github.com/pkg/errors -# github.com/prometheus/client_golang v1.19.1 -## explicit; go 1.20 -# github.com/prometheus/client_model v0.6.1 -## explicit; go 1.19 -# github.com/prometheus/common v0.53.0 -## explicit; go 1.20 -# github.com/prometheus/procfs v0.15.1 -## explicit; go 1.20 # github.com/rivo/uniseg v0.4.4 ## explicit; go 1.18 github.com/rivo/uniseg # github.com/ryanuber/go-glob v1.0.0 ## explicit github.com/ryanuber/go-glob -# github.com/sethvargo/go-password v0.3.0 -## explicit; go 1.22 # github.com/sirupsen/logrus v1.9.3 ## explicit; go 1.13 github.com/sirupsen/logrus @@ -225,10 +193,6 @@ github.com/spf13/pflag # github.com/vbatts/tar-split v0.11.5 ## explicit; go 1.17 github.com/vbatts/tar-split/archive/tar -# golang.org/x/crypto v0.24.0 -## explicit; go 1.18 -# golang.org/x/mod v0.18.0 -## explicit; go 1.18 # golang.org/x/net v0.26.0 ## explicit; go 1.18 golang.org/x/net/context @@ -240,11 +204,10 @@ golang.org/x/sync/errgroup golang.org/x/sys/execabs golang.org/x/sys/unix golang.org/x/sys/windows -# google.golang.org/protobuf v1.34.1 -## explicit; go 1.17 # gopkg.in/warnings.v0 v0.1.2 ## explicit gopkg.in/warnings.v0 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 +# github.com/openfaas/go-sdk => /home/welteki/code/openfaas/oss/go-sdk