From f4f56dc5d460ce17f75846a70570b63ea01d2e57 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Fri, 17 May 2024 16:06:27 +0200 Subject: [PATCH 1/6] Use OpenFaaS SDK to invoke functions Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/general.go | 63 ++++- commands/invoke.go | 154 +++++++++--- commands/invoke_test.go | 226 +++++++++++++----- go.mod | 21 +- go.sum | 139 +---------- vendor/github.com/openfaas/go-sdk/README.md | 71 ++++++ vendor/github.com/openfaas/go-sdk/client.go | 144 ++++++++--- .../go-sdk/client_credentials_auth.go | 70 ++---- vendor/github.com/openfaas/go-sdk/exchange.go | 74 +++++- .../github.com/openfaas/go-sdk/functions.go | 63 +++++ vendor/github.com/openfaas/go-sdk/iam_auth.go | 28 ++- vendor/github.com/openfaas/go-sdk/token.go | 36 ++- .../github.com/openfaas/go-sdk/tokencache.go | 82 +++++++ vendor/modules.txt | 41 +--- 14 files changed, 835 insertions(+), 377 deletions(-) create mode 100644 vendor/github.com/openfaas/go-sdk/functions.go create mode 100644 vendor/github.com/openfaas/go-sdk/tokencache.go diff --git a/commands/general.go b/commands/general.go index eb0e8d498..f19dbce38 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" ) @@ -49,21 +50,67 @@ func GetDefaultSDKClient() (*sdk.Client, error) { return nil, err } - transport := GetDefaultCLITransport(tlsInsecure, &commandTimeout) + authConfig, err := config.LookupAuthConfig(gatewayURL.String()) + if err != nil { + return nil, fmt.Errorf("failed to lookup auth config: %w", 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 66d663bfb..03668969f 100644 --- a/commands/invoke.go +++ b/commands/invoke.go @@ -4,14 +4,18 @@ 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" ) @@ -63,32 +67,32 @@ var invokeCmd = &cobra.Command{ } 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] 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 +105,126 @@ 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 -} + authenticate := false + res, err := client.InvokeFunction(functionName, functionInvokeNamespace, invokeAsync, authenticate, req) + if err != nil { + return fmt.Errorf("cannot connect to OpenFaaS on URL: %s", client.GatewayURL) + } -func generateSignedHeader(message []byte, key string, headerName string) (string, error) { + if res.Body != nil { + defer res.Body.Close() + } - if len(headerName) == 0 { - return "", fmt.Errorf("signed header must have a non-zero length") + 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)) + } + + if invokeAsync && res.StatusCode == http.StatusAccepted { + fmt.Fprintf(os.Stderr, "Function submitted asynchronously.\n") + return nil } + 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 +} diff --git a/commands/invoke_test.go b/commands/invoke_test.go index 4cac702b1..f7263bd3c 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" ) @@ -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) - } - - if test.expectedErr == false { - - encodedHash := strings.SplitN(test.expectedSig, "=", 2) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := generateSignature(test.message, test.key) - 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,148 @@ 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") + } + }) + } +} diff --git a/go.mod b/go.mod index 65283655d..a3cab6d47 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 9b88955ec..ecf4b8cdf 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 9ef89847f..3f2ce3b25 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 ef21fa2f4..9ca378f6b 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 0696eea42..302650463 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 db13ff277..36c37f6c2 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 000000000..ae14c9ecf --- /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 e672beda3..8760334cd 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 981fc534e..2b48a73ac 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 000000000..993524543 --- /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 8bb5fc879..0387b9b8c 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 From da19738c55d2fabbdb551e3be596890585d2e5f6 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Tue, 21 May 2024 16:58:14 +0200 Subject: [PATCH 2/6] Detect if a function needs authentication Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/invoke.go | 54 ++++++++++++++++++++++++++++++++++++++--- commands/invoke_test.go | 32 ++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/commands/invoke.go b/commands/invoke.go index 03668969f..3ac9516f6 100644 --- a/commands/invoke.go +++ b/commands/invoke.go @@ -28,8 +28,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") @@ -40,6 +43,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") @@ -124,16 +128,39 @@ func runInvoke(cmd *cobra.Command, args []string) error { } req.Header = httpHeader - authenticate := false res, err := client.InvokeFunction(functionName, functionInvokeNamespace, invokeAsync, authenticate, req) if err != nil { - return fmt.Errorf("cannot connect to OpenFaaS on URL: %s", client.GatewayURL) + 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 { @@ -228,3 +255,24 @@ func validateHTTPMethod(httpMethod string) error { } 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 f7263bd3c..45f90b584 100644 --- a/commands/invoke_test.go +++ b/commands/invoke_test.go @@ -313,3 +313,35 @@ func Test_parseQueryValues_invalid(t *testing.T) { }) } } + +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) + } + } +} From dfc983d809f7172ce301d9d00e4bb34715a62cd2 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Wed, 29 May 2024 14:52:15 +0200 Subject: [PATCH 3/6] Print warning when auth config lookup fails Don't exit the program when looking up the auth config fails but print a warning instead. Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/general.go | 2 +- commands/invoke_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/general.go b/commands/general.go index f19dbce38..bee78c689 100644 --- a/commands/general.go +++ b/commands/general.go @@ -52,7 +52,7 @@ func GetDefaultSDKClient() (*sdk.Client, error) { authConfig, err := config.LookupAuthConfig(gatewayURL.String()) if err != nil { - return nil, fmt.Errorf("failed to lookup auth config: %w", err) + fmt.Printf("Failed to lookup auth config: %s\n", err) } var clientAuth sdk.ClientAuth diff --git a/commands/invoke_test.go b/commands/invoke_test.go index 45f90b584..d2d3961cf 100644 --- a/commands/invoke_test.go +++ b/commands/invoke_test.go @@ -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, }, }) From c2840ef323cbfdcee3728d92dc3594cc47313fdb Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Mon, 17 Jun 2024 13:18:14 +0200 Subject: [PATCH 4/6] Fix default sdk client gateway url Ensure the default sdk client reads the provider url from the stack file if the '--yaml/-f' flag is used. Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/general.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/commands/general.go b/commands/general.go index bee78c689..16cea8a50 100644 --- a/commands/general.go +++ b/commands/general.go @@ -10,6 +10,7 @@ import ( "time" "github.com/openfaas/faas-cli/config" + "github.com/openfaas/faas-cli/stack" "github.com/openfaas/go-sdk" ) @@ -44,7 +45,11 @@ func GetDefaultCLITransport(tlsInsecure bool, timeout *time.Duration) *http.Tran } func GetDefaultSDKClient() (*sdk.Client, error) { - gatewayAddress := getGatewayURL(gateway, defaultGateway, "", os.Getenv(openFaaSURLEnvironment)) + gatewayAddress, err := getGatewayAddress() + if err != nil { + return nil, err + } + gatewayURL, err := url.Parse(gatewayAddress) if err != nil { return nil, err @@ -102,6 +107,22 @@ func GetDefaultSDKClient() (*sdk.Client, error) { ), nil } +func getGatewayAddress() (string, error) { + var yamlUrl string + if len(yamlFile) > 0 { + parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter, envsubst) + if err != nil { + return "", err + } + + if parsedServices != nil { + yamlUrl = parsedServices.Provider.GatewayURL + } + } + + return getGatewayURL(gateway, defaultGateway, yamlUrl, os.Getenv(openFaaSURLEnvironment)), nil +} + type StaticTokenAuth struct { token string } From ea7f4c427b90fe33107578943771e2e8c8e22cba Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Tue, 18 Jun 2024 13:58:45 +0200 Subject: [PATCH 5/6] Get function namespace from stack file Try to lookup the function namespace in the stack file before falling back to the default namespace. This is the default behaviour for all other commands and should be implemeted for invoke as well. Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/faas.go | 4 ++++ commands/general.go | 24 ++++-------------------- commands/invoke.go | 24 +++++++++++++++++++++++- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/commands/faas.go b/commands/faas.go index 709a811be..16c1355d3 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 16cea8a50..c8cbb54e9 100644 --- a/commands/general.go +++ b/commands/general.go @@ -10,7 +10,6 @@ import ( "time" "github.com/openfaas/faas-cli/config" - "github.com/openfaas/faas-cli/stack" "github.com/openfaas/go-sdk" ) @@ -45,11 +44,12 @@ func GetDefaultCLITransport(tlsInsecure bool, timeout *time.Duration) *http.Tran } func GetDefaultSDKClient() (*sdk.Client, error) { - gatewayAddress, err := getGatewayAddress() - if err != nil { - return nil, err + 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 @@ -107,22 +107,6 @@ func GetDefaultSDKClient() (*sdk.Client, error) { ), nil } -func getGatewayAddress() (string, error) { - var yamlUrl string - if len(yamlFile) > 0 { - parsedServices, err := stack.ParseYAMLFile(yamlFile, regex, filter, envsubst) - if err != nil { - return "", err - } - - if parsedServices != nil { - yamlUrl = parsedServices.Provider.GatewayURL - } - } - - return getGatewayURL(gateway, defaultGateway, yamlUrl, os.Getenv(openFaaSURLEnvironment)), nil -} - type StaticTokenAuth struct { token string } diff --git a/commands/invoke.go b/commands/invoke.go index 3ac9516f6..77716ff83 100644 --- a/commands/invoke.go +++ b/commands/invoke.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/alexellis/hmac" + "github.com/openfaas/faas-cli/stack" "github.com/openfaas/faas-cli/version" "github.com/spf13/cobra" ) @@ -68,6 +69,17 @@ 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 { @@ -76,6 +88,16 @@ func runInvoke(cmd *cobra.Command, args []string) error { } 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 ") } @@ -128,7 +150,7 @@ func runInvoke(cmd *cobra.Command, args []string) error { } req.Header = httpHeader - res, err := client.InvokeFunction(functionName, functionInvokeNamespace, invokeAsync, authenticate, req) + res, err := client.InvokeFunction(functionName, functionNamespace, invokeAsync, authenticate, req) if err != nil { return fmt.Errorf("failed to invoke function: %s", err) } From 7eae67b6ea63cfff7a397463ede28b4fcfe7e7e8 Mon Sep 17 00:00:00 2001 From: "Han Verstraete (OpenFaaS Ltd)" Date: Tue, 18 Jun 2024 17:57:26 +0200 Subject: [PATCH 6/6] Set openfaas env variables with local-run Set env variables that are normally injects by the provider like, 'OPENFAAS_NAME' and 'OPENFAAS_NAMESPACE' when running function with local-run. This change makes it possible to use built-in function authentication with local-run. The 'jwt_auth_local' env variable is to true by default to help with this. Signed-off-by: Han Verstraete (OpenFaaS Ltd) --- commands/local_run.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/commands/local_run.go b/commands/local_run.go index 024636d7f..6cdf2a9e8 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 }