Allow dot in function name

This patch enables the use-case for multiple namepsaces by
allowing a dot to be used in the function name.

dep has been run to update OpenFaaS projects and also to
prune unused files.

Tested by doing a build.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd)
2019-09-20 11:12:19 +01:00
committed by Alex Ellis
parent dc3c5fb9b3
commit 0a90125aba
1298 changed files with 86 additions and 629745 deletions

View File

@ -1 +0,0 @@
redirect: https://raw.githubusercontent.com/openfaas/faas/master/.DEREK.yml

View File

@ -1,19 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Goland IDE
.idea
faas-backend

View File

@ -1,4 +0,0 @@
language: go
go_import_path: github.com/openfaas/faas-provider
script:
- make test

View File

@ -1,25 +0,0 @@
FROM golang:1.10.4-alpine3.8
RUN mkdir -p /go/src/github.com/openfaas/faas-provider/
WORKDIR /go/src/github.com/openfaas/faas-provider
COPY vendor vendor
COPY types types
COPY auth auth
COPY serve.go .
RUN go test ./auth/ -v \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o faas-provider .
FROM alpine:3.8
RUN apk --no-cache add ca-certificates
WORKDIR /root/
EXPOSE 8080
ENV http_proxy ""
ENV https_proxy ""
COPY --from=0 /go/src/github.com/openfaas/faas-provider/faas-provider .
CMD ["./faas-provider]

View File

@ -1,39 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:160eabf7a69910fd74f29c692718bc2437c1c1c7d4c9dea9712357752a70e5df"
name = "github.com/gorilla/context"
packages = ["."]
pruneopts = "UT"
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
[[projects]]
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
name = "github.com/gorilla/mux"
packages = ["."]
pruneopts = "UT"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
digest = "1:9a1bb99a85e2ccddbc593aa0af084cdf6ea18ed081469eb90e7f6d0d303af6cd"
name = "go.uber.org/goleak"
packages = [
".",
"internal/stack",
]
pruneopts = "UT"
revision = "1ac8aeca0a53163331564467638f6ffb639636bf"
version = "v0.10.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/gorilla/mux",
"go.uber.org/goleak",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,7 +0,0 @@
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.2"

View File

@ -1,6 +0,0 @@
build:
docker build -t faas-provider .
test :
go test -cover ./...

View File

@ -1,53 +0,0 @@
faas-provider
==============
This faas-provider can be used to write your own back-end for OpenFaaS. The Golang SDK can be vendored into your project so that you can provide a provider which is compliant and compatible with the OpenFaaS gateway.
![Conceptual diagram](docs/conceptual.png)
The faas-provider provides CRUD for functions and an invoke capability. If you complete the required endpoints then you will be able to use your container orchestrator or back-end system with the existing OpenFaaS ecosystem and tooling.
> See also: [backends guide](https://github.com/openfaas/faas/blob/master/guide/deprecated/backends.md)
### Recommendations
The following is used in OpenFaaS and recommended for those seeking to build their own back-ends:
* License: MIT
* Language: Golang
### How to use this project
All the required HTTP routes are configured automatically including a HTTP server on port 8080. Your task is to implement the supplied HTTP handler functions.
For an example see the [server.go](https://github.com/openfaas/faas-netes/blob/master/server.go) file in the [faas-netes](https://github.com/openfaas/faas-netes) Kubernetes backend.
I.e.:
```go
timeout := 8 * time.Second
bootstrapHandlers := bootTypes.FaaSHandlers{
FunctionProxy: handlers.MakeProxy(),
DeleteHandler: handlers.MakeDeleteHandler(clientset),
DeployHandler: handlers.MakeDeployHandler(clientset),
FunctionReader: handlers.MakeFunctionReader(clientset),
ReplicaReader: handlers.MakeReplicaReader(clientset),
ReplicaUpdater: handlers.MakeReplicaUpdater(clientset),
InfoHandler: handlers.MakeInfoHandler(),
LogHandler: logs.NewLogHandlerFunc(requestor,timeout),
}
var port int
port = 8080
bootstrapConfig := bootTypes.FaaSConfig{
ReadTimeout: timeout,
WriteTimeout: timeout,
TCPPort: &port,
}
bootstrap.Serve(&bootstrapHandlers, &bootstrapConfig)
```
### Need help?
Join `#faas-provider` on [OpenFaaS Slack](https://docs.openfaas.com/community/)

View File

@ -1,66 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package auth
import (
"io"
"net/http"
"net/http/httptest"
"testing"
)
func Test_AuthWithValidPassword_Gives200(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
w := httptest.NewRecorder()
wantUser := "admin"
wantPassword := "password"
r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
r.SetBasicAuth(wantUser, wantPassword)
wantCredentials := &BasicAuthCredentials{
User: wantUser,
Password: wantPassword,
}
decorated := DecorateWithBasicAuth(handler, wantCredentials)
decorated.ServeHTTP(w, r)
wantCode := http.StatusOK
if w.Code != wantCode {
t.Errorf("status code, want: %d, got: %d", wantCode, w.Code)
t.Fail()
}
}
func Test_AuthWithInvalidPassword_Gives403(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "<html><body>Hello World!</body></html>")
}
w := httptest.NewRecorder()
wantUser := "admin"
wantPassword := "test"
r := httptest.NewRequest(http.MethodGet, "http://localhost:8080", nil)
r.SetBasicAuth(wantUser, wantPassword)
wantCredentials := &BasicAuthCredentials{
User: wantUser,
Password: "",
}
decorated := DecorateWithBasicAuth(handler, wantCredentials)
decorated.ServeHTTP(w, r)
wantCode := http.StatusUnauthorized
if w.Code != wantCode {
t.Errorf("status code, want: %d, got: %d", wantCode, w.Code)
t.Fail()
}
}

View File

@ -1,64 +0,0 @@
// Copyright (c) OpenFaaS Author(s). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package auth
import (
"io/ioutil"
"os"
"path"
"testing"
)
func Test_ReadFromCustomLocation_AndNames(t *testing.T) {
tmp := os.TempDir()
userWant := "admin"
ioutil.WriteFile(path.Join(tmp, "user.txt"), []byte(userWant), 0700)
passWant := "test1234"
ioutil.WriteFile(path.Join(tmp, "pass.txt"), []byte(passWant), 0700)
reader := ReadBasicAuthFromDisk{
SecretMountPath: tmp,
UserFilename: "user.txt",
PasswordFilename: "pass.txt",
}
creds, err := reader.Read()
if err != nil {
t.Errorf("can't read secrets: %s", err.Error())
}
if creds.User != userWant {
t.Errorf("user, want: %s, got %s", userWant, creds.User)
}
if creds.Password != passWant {
t.Errorf("password, want: %s, got %s", passWant, creds.Password)
}
}
func Test_ReadFromCustomLocation_DefaultNames(t *testing.T) {
tmp := os.TempDir()
userWant := "admin"
ioutil.WriteFile(path.Join(tmp, "basic-auth-user"), []byte(userWant), 0700)
passWant := "test1234"
ioutil.WriteFile(path.Join(tmp, "basic-auth-password"), []byte(passWant), 0700)
reader := ReadBasicAuthFromDisk{
SecretMountPath: tmp,
}
creds, err := reader.Read()
if err != nil {
t.Errorf("can't read secrets: %s", err.Error())
}
if creds.User != userWant {
t.Errorf("user, want: %s, got %s", userWant, creds.User)
}
if creds.Password != passWant {
t.Errorf("password, want: %s, got %s", passWant, creds.Password)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

View File

@ -1,12 +0,0 @@
package httputil
import (
"fmt"
"net/http"
)
// Errorf sets the response status code and write formats the provided message as the
// response body
func Errorf(w http.ResponseWriter, statusCode int, msg string, args ...interface{}) {
http.Error(w, fmt.Sprintf(msg, args...), statusCode)
}

View File

@ -1,2 +0,0 @@
# Static Log Server Example
This example shows a very basic static log server. It will return the same 3 log messages for every request.

View File

@ -1,47 +0,0 @@
package main
import (
"context"
"net/http"
"time"
"github.com/openfaas/faas-provider/logs"
)
// staticLogRequestor implements the logs Requestor returning a static stream of logs
type staticLogRequestor struct {
logs []string
}
func (s staticLogRequestor) Query(ctx context.Context, r logs.Request) (<-chan logs.Message, error) {
resp := make(chan logs.Message, len(s.logs))
// A real implementation would possibly run a query to their log storage here, if it returns a
// channel, and pass that channel to the go routine below instead of ranging over `s.logs`
// If the log storage backend client does not return a channel, the query would need to
// occur at the beginning of the goroutine below ... or a separate goroutine
go func() {
for _, m := range s.logs {
// always watch the ctx to timeout/cancel/finish
if ctx.Err() != nil {
return
}
resp <- logs.Message{
Name: r.Name,
Instance: "fake",
Timestamp: time.Now(),
Text: m,
}
}
}()
return resp, nil
}
func main() {
requestor := staticLogRequestor{logs: []string{"msg1", "msg2", "something interesting"}}
http.HandleFunc("/system/logs", logs.NewLogHandlerFunc(requestor, 10*time.Second))
http.ListenAndServe(":80", nil)
}

View File

@ -1,142 +0,0 @@
package logs
import (
"context"
"encoding/json"
"log"
"net/http"
"net/url"
"strconv"
"time"
"github.com/openfaas/faas-provider/httputil"
)
// Requester submits queries the logging system.
// This will be passed to the log handler constructor.
type Requester interface {
// Query submits a log request to the actual logging system.
Query(context.Context, Request) (<-chan Message, error)
}
// NewLogHandlerFunc creates an http HandlerFunc from the supplied log Requestor.
func NewLogHandlerFunc(requestor Requester, timeout time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}
cn, ok := w.(http.CloseNotifier)
if !ok {
log.Println("LogHandler: response is not a CloseNotifier, required for streaming response")
http.NotFound(w, r)
return
}
flusher, ok := w.(http.Flusher)
if !ok {
log.Println("LogHandler: response is not a Flusher, required for streaming response")
http.NotFound(w, r)
return
}
logRequest, err := parseRequest(r)
if err != nil {
log.Printf("LogHandler: could not parse request %s", err)
httputil.Errorf(w, http.StatusUnprocessableEntity, "could not parse the log request")
return
}
ctx, cancelQuery := context.WithTimeout(r.Context(), timeout)
defer cancelQuery()
messages, err := requestor.Query(ctx, logRequest)
if err != nil {
// add smarter error handling here
httputil.Errorf(w, http.StatusInternalServerError, "function log request failed")
return
}
// Send the initial headers saying we're gonna stream the response.
w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set(http.CanonicalHeaderKey("Content-Type"), "application/x-ndjson")
w.WriteHeader(http.StatusOK)
flusher.Flush()
// ensure that we always try to send the closing chunk, not the inverted order due to how
// the defer stack works. We need two flush statements to ensure that the empty slice is
// sent as its own chunk
defer flusher.Flush()
defer w.Write([]byte{})
defer flusher.Flush()
jsonEncoder := json.NewEncoder(w)
for messages != nil {
select {
case <-cn.CloseNotify():
log.Println("LogHandler: client stopped listening")
return
case msg, ok := <-messages:
if !ok {
log.Println("LogHandler: end of log stream")
messages = nil
return
}
// serialize and write the msg to the http ResponseWriter
err := jsonEncoder.Encode(msg)
if err != nil {
// can't actually write the status header here so we should json serialize an error
// and return that because we have already sent the content type and status code
log.Printf("LogHandler: failed to serialize log message: '%s'\n", msg.String())
log.Println(err.Error())
// write json error message here ?
jsonEncoder.Encode(Message{Text: "failed to serialize log message"})
flusher.Flush()
return
}
flusher.Flush()
}
}
return
}
}
// parseRequest extracts the logRequest from the GET variables or from the POST body
func parseRequest(r *http.Request) (logRequest Request, err error) {
query := r.URL.Query()
logRequest.Name = getValue(query, "name")
logRequest.Instance = getValue(query, "instance")
tailStr := getValue(query, "tail")
if tailStr != "" {
logRequest.Tail, err = strconv.Atoi(tailStr)
if err != nil {
return logRequest, err
}
}
// ignore error because it will default to false if we can't parse it
logRequest.Follow, _ = strconv.ParseBool(getValue(query, "follow"))
sinceStr := getValue(query, "since")
if sinceStr != "" {
since, err := time.Parse(time.RFC3339, sinceStr)
logRequest.Since = &since
if err != nil {
return logRequest, err
}
}
return logRequest, nil
}
// getValue returns the value for the given key. If the key has more than one value, it returns the
// last value. if the value does not exist, it returns the empty string.
func getValue(queryValues url.Values, name string) string {
values := queryValues[name]
if len(values) == 0 {
return ""
}
return values[len(values)-1]
}

View File

@ -1,191 +0,0 @@
package logs
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"go.uber.org/goleak"
)
var queryTimeout = 30 * time.Second
func Test_logsHandlerDoesNotLeakGoroutinesWhenProviderClosesStream(t *testing.T) {
defer goleak.VerifyNoLeaks(t)
msgs := []Message{
Message{Name: "funcFoo", Text: "msg 0"},
Message{Name: "funcFoo", Text: "msg 1"},
}
var expected bytes.Buffer
json.NewEncoder(&expected).Encode(msgs[0])
json.NewEncoder(&expected).Encode(msgs[1])
querier := newFakeQueryRequester(msgs, nil)
logHandler := NewLogHandlerFunc(querier, queryTimeout)
testSrv := httptest.NewServer(http.HandlerFunc(logHandler))
defer testSrv.Close()
resp, err := http.Get(testSrv.URL + "?name=funcFoo")
if err != nil {
t.Fatalf("unexpected error sending log request: %s", err)
}
querier.Close()
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error reading log response: %s", err)
}
if string(body) != expected.String() {
t.Fatalf("expected log message %s, got: %s", expected.String(), body)
}
}
func Test_logsHandlerDoesNotLeakGoroutinesWhenClientClosesConnection(t *testing.T) {
defer goleak.VerifyNoLeaks(t)
msgs := []Message{
Message{Name: "funcFoo", Text: "msg 0"},
Message{Name: "funcFoo", Text: "msg 1"},
}
querier := newFakeQueryRequester(msgs, nil)
logHandler := NewLogHandlerFunc(querier, queryTimeout)
testSrv := httptest.NewServer(http.HandlerFunc(logHandler))
defer testSrv.Close()
reqContext, cancel := context.WithCancel(context.Background())
req, _ := http.NewRequest(http.MethodGet, testSrv.URL+"?name=funcFoo", nil)
req = req.WithContext(reqContext)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unexpected error sending log request: %s", err)
}
go func() {
defer resp.Body.Close()
_, err := ioutil.ReadAll(resp.Body)
if err != context.Canceled {
t.Fatalf("unexpected error reading log response: %s", err)
}
}()
cancel()
}
func Test_GETRequestParsing(t *testing.T) {
sinceTime, _ := time.Parse(time.RFC3339, "2019-02-16T09:10:06+00:00")
scenarios := []struct {
name string
rawQueryStr string
err string
expectedRequest Request
}{
{
name: "empty query creates an empty request",
rawQueryStr: "",
err: "",
expectedRequest: Request{},
},
{
name: "name only query",
rawQueryStr: "name=foobar",
err: "",
expectedRequest: Request{Name: "foobar"},
},
{
name: "name only query",
rawQueryStr: "name=foobar",
err: "",
expectedRequest: Request{Name: "foobar"},
},
{
name: "multiple name values selects the last value",
rawQueryStr: "name=foobar&name=theactual name",
err: "",
expectedRequest: Request{Name: "theactual name"},
},
{
name: "valid request with every parameter",
rawQueryStr: "name=foobar&since=2019-02-16T09%3A10%3A06%2B00%3A00&tail=5&follow=true",
err: "",
expectedRequest: Request{
Name: "foobar",
Since: &sinceTime,
Tail: 5,
Follow: true,
},
},
}
req := httptest.NewRequest(http.MethodGet, "/", nil)
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
req.URL.RawQuery = s.rawQueryStr
logRequest, err := parseRequest(req)
equalError(t, s.err, err)
if logRequest.String() != s.expectedRequest.String() {
t.Errorf("expected log request: %s, got: %s", s.expectedRequest, logRequest)
}
})
}
}
func equalError(t *testing.T, expected string, actual error) {
if expected == "" && actual == nil {
return
}
if expected == "" && actual != nil {
t.Errorf("unexpected error: %s", actual.Error())
return
}
if actual.Error() != expected {
t.Errorf("expected error: %s got: %s", expected, actual.Error())
}
}
type fakeQueryRequester struct {
Logs []Message
err error
stream chan Message
}
func (r fakeQueryRequester) Close() {
close(r.stream)
r.stream = nil
}
func (r fakeQueryRequester) Query(context.Context, Request) (<-chan Message, error) {
if r.err != nil {
return nil, r.err
}
for _, m := range r.Logs {
r.stream <- m
}
return r.stream, nil
}
func newFakeQueryRequester(l []Message, err error) fakeQueryRequester {
return fakeQueryRequester{
Logs: l,
err: err,
stream: make(chan Message, len(l)),
}
}

View File

@ -1,50 +0,0 @@
// Package logs provides the standard interface and handler for OpenFaaS providers to expose function logs.
//
// The package defines the Requester interface that OpenFaaS providers should implement and then expose using
// the predefined NewLogHandlerFunc. See the example folder for a minimal log provider implementation.
//
// The Requester is where the actual specific logic for connecting to and querying the log system should be implemented.
//
package logs
import (
"fmt"
"time"
)
// Request is the query to return the function logs.
type Request struct {
// Name is the function name and is required
Name string `json:"name"`
// Instance is the optional container name, that allows you to request logs from a specific function instance
Instance string `json:"instance"`
// Since is the optional datetime value to start the logs from
Since *time.Time `json:"since"`
// Tail sets the maximum number of log messages to return, <=0 means unlimited
Tail int `json:"tail"`
// Follow is allows the user to request a stream of logs until the timeout
Follow bool `json:"follow"`
}
// String implements that Stringer interface and prints the log Request in a consistent way that
// allows you to safely compare if two requests have the same value.
func (r Request) String() string {
return fmt.Sprintf("name:%s instance:%s since:%v tail:%d follow:%v", r.Name, r.Instance, r.Since, r.Tail, r.Follow)
}
// Message is a specific log message from a function container log stream
type Message struct {
// Name is the function name
Name string `json:"name"`
// instance is the name/id of the specific function instance
Instance string `json:"instance"`
// Timestamp is the timestamp of when the log message was recorded
Timestamp time.Time `json:"timestamp"`
// Text is the raw log message content
Text string `json:"text"`
}
// String implements the Stringer interface and allows for nice and simple string formatting of a log Message.
func (m Message) String() string {
return fmt.Sprintf("%s %s (%s) %s", m.Timestamp.String(), m.Name, m.Instance, m.Text)
}

View File

@ -1,134 +0,0 @@
package proxy
import (
"bytes"
"errors"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
)
type testBaseURLResolver struct {
testServerBase string
err error
}
func (tr *testBaseURLResolver) Resolve(name string) (url.URL, error) {
if tr.err != nil {
return url.URL{}, tr.err
}
return url.URL{
Scheme: "http",
Host: tr.testServerBase,
}, nil
}
func Test_NewHandlerFunc_Panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("should panic if resolver is nil")
}
}()
NewHandlerFunc(time.Second, nil)
}
func Test_NewHandlerFunc_NoPanic(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("should not panic if resolver is not nil")
}
}()
proxyFunc := NewHandlerFunc(time.Second, &testBaseURLResolver{})
if proxyFunc == nil {
t.Errorf("proxy handler func is nil")
}
}
func Test_ProxyHandler_NonAllowedMethods(t *testing.T) {
proxyFunc := NewHandlerFunc(time.Second, &testBaseURLResolver{})
nonAllowedMethods := []string{
http.MethodHead, http.MethodConnect, http.MethodOptions, http.MethodTrace,
}
for _, method := range nonAllowedMethods {
t.Run(method+" method is not allowed", func(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(method, "http://example.com/foo", nil)
proxyFunc(w, req)
resp := w.Result()
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("expected status code `%d`, got `%d`", http.StatusMethodNotAllowed, resp.StatusCode)
}
})
}
}
func Test_ProxyHandler_MissingFunctionNameError(t *testing.T) {
proxyFunc := NewHandlerFunc(time.Second, &testBaseURLResolver{"", nil})
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
req = mux.SetURLVars(req, map[string]string{"name": ""})
proxyFunc(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("expected status code `%d`, got `%d`", http.StatusBadRequest, w.Code)
}
respBody := strings.TrimSpace(w.Body.String())
if respBody != errMissingFunctionName {
t.Errorf("expected error message `%s`, got `%s`", errMissingFunctionName, respBody)
}
}
func Test_ProxyHandler_ResolveError(t *testing.T) {
logs := &bytes.Buffer{}
log.SetOutput(logs)
resolveErr := errors.New("can not find test service `foo`")
proxyFunc := NewHandlerFunc(time.Second, &testBaseURLResolver{"", resolveErr})
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
req = mux.SetURLVars(req, map[string]string{"name": "foo"})
proxyFunc(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("expected status code `%d`, got `%d`", http.StatusBadRequest, w.Code)
}
respBody := strings.TrimSpace(w.Body.String())
if respBody != "Cannot find service: foo." {
t.Errorf("expected error message `%s`, got `%s`", "Cannot find service: foo.", respBody)
}
if !strings.Contains(logs.String(), resolveErr.Error()) {
t.Errorf("expected logs to contain `%s`", resolveErr.Error())
}
}
func Test_ProxyHandler_Proxy_Success(t *testing.T) {
t.Skip("Test not implemented yet")
// testFuncService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusOK)
// }))
// proxyFunc := NewHandlerFunc(time.Second, &testBaseURLResolver{testFuncService.URL, nil})
// w := httptest.NewRecorder()
// req := httptest.NewRequest("GET", "http://example.com/foo", nil)
// req = mux.SetURLVars(req, map[string]string{"name": "foo"})
// proxyFunc(w, req)
}

View File

@ -1,216 +0,0 @@
// Package proxy provides a default function invocation proxy method for OpenFaaS providers.
//
// The function proxy logic is used by the Gateway when `direct_functions` is set to false.
// This means that the provider will direct call the function and return the results. This
// involves resolving the function by name and then copying the result into the original HTTP
// request.
//
// openfaas-provider has implemented a standard HTTP HandlerFunc that will handle setting
// timeout values, parsing the request path, and copying the request/response correctly.
// bootstrapHandlers := bootTypes.FaaSHandlers{
// FunctionProxy: proxy.NewHandlerFunc(timeout, resolver),
// DeleteHandler: handlers.MakeDeleteHandler(clientset),
// DeployHandler: handlers.MakeDeployHandler(clientset),
// FunctionReader: handlers.MakeFunctionReader(clientset),
// ReplicaReader: handlers.MakeReplicaReader(clientset),
// ReplicaUpdater: handlers.MakeReplicaUpdater(clientset),
// InfoHandler: handlers.MakeInfoHandler(),
// }
//
// proxy.NewHandlerFunc is optional, but does simplify the logic of your provider.
package proxy
import (
"io"
"log"
"net"
"net/http"
"net/url"
"time"
"github.com/gorilla/mux"
"github.com/openfaas/faas-provider/httputil"
)
const (
watchdogPort = "8080"
defaultContentType = "text/plain"
errMissingFunctionName = "Please provide a valid route /function/function_name."
)
// BaseURLResolver URL resolver for proxy requests
//
// The FaaS provider implementation is responsible for providing the resolver function implementation.
// BaseURLResolver.Resolve will receive the function name and should return the URL of the
// function service.
type BaseURLResolver interface {
Resolve(functionName string) (url.URL, error)
}
// NewHandlerFunc creates a standard http.HandlerFunc to proxy function requests.
// The returned http.HandlerFunc will ensure:
//
// - proper proxy request timeouts
// - proxy requests for GET, POST, PATCH, PUT, and DELETE
// - path parsing including support for extracing the function name, sub-paths, and query paremeters
// - passing and setting the `X-Forwarded-Host` and `X-Forwarded-For` headers
// - logging errors and proxy request timing to stdout
//
// Note that this will panic if `resolver` is nil.
func NewHandlerFunc(timeout time.Duration, resolver BaseURLResolver) http.HandlerFunc {
if resolver == nil {
panic("NewHandlerFunc: empty proxy handler resolver, cannot be nil")
}
proxyClient := http.Client{
// these Transport values ensure that the http Client will eventually timeout and prevents
// infinite retries. The default http.Client configure these timeouts. The specific
// values tuned via performance testing/benchmarking
//
// Additional context can be found at
// - https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
// - https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: timeout,
KeepAlive: 1 * time.Second,
}).DialContext,
IdleConnTimeout: 120 * time.Millisecond,
ExpectContinueTimeout: 1500 * time.Millisecond,
},
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}
switch r.Method {
case http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
http.MethodGet:
proxyRequest(w, r, proxyClient, resolver)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
}
// proxyRequest handles the actual resolution of and then request to the function service.
func proxyRequest(w http.ResponseWriter, originalReq *http.Request, proxyClient http.Client, resolver BaseURLResolver) {
ctx := originalReq.Context()
pathVars := mux.Vars(originalReq)
functionName := pathVars["name"]
if functionName == "" {
httputil.Errorf(w, http.StatusBadRequest, errMissingFunctionName)
return
}
functionAddr, resolveErr := resolver.Resolve(functionName)
if resolveErr != nil {
// TODO: Should record the 404/not found error in Prometheus.
log.Printf("resolver error: cannot find %s: %s\n", functionName, resolveErr.Error())
httputil.Errorf(w, http.StatusNotFound, "Cannot find service: %s.", functionName)
return
}
proxyReq, err := buildProxyRequest(originalReq, functionAddr, pathVars["params"])
if err != nil {
httputil.Errorf(w, http.StatusInternalServerError, "Failed to resolve service: %s.", functionName)
return
}
if proxyReq.Body != nil {
defer proxyReq.Body.Close()
}
start := time.Now()
response, err := proxyClient.Do(proxyReq.WithContext(ctx))
seconds := time.Since(start)
if err != nil {
log.Printf("error with proxy request to: %s, %s\n", proxyReq.URL.String(), err.Error())
httputil.Errorf(w, http.StatusInternalServerError, "Can't reach service for: %s.", functionName)
return
}
log.Printf("%s took %f seconds\n", functionName, seconds.Seconds())
clientHeader := w.Header()
copyHeaders(clientHeader, &response.Header)
w.Header().Set("Content-Type", getContentType(response.Header, originalReq.Header))
w.WriteHeader(response.StatusCode)
io.Copy(w, response.Body)
}
// buildProxyRequest creates a request object for the proxy request, it will ensure that
// the original request headers are preserved as well as setting openfaas system headers
func buildProxyRequest(originalReq *http.Request, baseURL url.URL, extraPath string) (*http.Request, error) {
host := baseURL.Host
if baseURL.Port() == "" {
host = baseURL.Host + ":" + watchdogPort
}
url := url.URL{
Scheme: baseURL.Scheme,
Host: host,
Path: extraPath,
RawQuery: originalReq.URL.RawQuery,
}
upstreamReq, err := http.NewRequest(originalReq.Method, url.String(), nil)
if err != nil {
return nil, err
}
copyHeaders(upstreamReq.Header, &originalReq.Header)
if len(originalReq.Host) > 0 && upstreamReq.Header.Get("X-Forwarded-Host") == "" {
upstreamReq.Header["X-Forwarded-Host"] = []string{originalReq.Host}
}
if upstreamReq.Header.Get("X-Forwarded-For") == "" {
upstreamReq.Header["X-Forwarded-For"] = []string{originalReq.RemoteAddr}
}
if originalReq.Body != nil {
upstreamReq.Body = originalReq.Body
}
return upstreamReq, nil
}
// copyHeaders clones the header values from the source into the destination.
func copyHeaders(destination http.Header, source *http.Header) {
for k, v := range *source {
vClone := make([]string, len(v))
copy(vClone, v)
destination[k] = vClone
}
}
// getContentType resolves the correct Content-Type for a proxied function.
func getContentType(request http.Header, proxyResponse http.Header) (headerContentType string) {
responseHeader := proxyResponse.Get("Content-Type")
requestHeader := request.Get("Content-Type")
if len(responseHeader) > 0 {
headerContentType = responseHeader
} else if len(requestHeader) > 0 {
headerContentType = requestHeader
} else {
headerContentType = defaultContentType
}
return headerContentType
}

View File

@ -1,439 +0,0 @@
package proxy
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
)
func varHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("name: %s params: %s", vars["name"], vars["params"])))
}
func testResolver(functionName string) (url.URL, error) {
return url.URL{
Scheme: "http",
Host: functionName,
}, nil
}
type mockResolver struct {
u *url.URL
err error
}
func (m mockResolver) Resolve(name string) (url.URL, error) {
if m.u != nil {
return *m.u, m.err
}
return url.URL{}, m.err
}
func Test_ProxyHandler_StatusCode(t *testing.T) {
testcases := []int{200, 204, 400, 409, 422, 500, 503}
for _, tc := range testcases {
t.Run(fmt.Sprintf("returns %d when upstream returns %d", tc, tc), func(t *testing.T) {
// upstream represents the will be resolved and the function call then sent to
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tc > 399 {
http.Error(w, "upstream error", tc)
return
}
w.WriteHeader(tc)
}))
u, err := url.Parse(upstream.URL)
proxyHandler := NewHandlerFunc(time.Second, mockResolver{u, err})
rr := httptest.NewRecorder()
req, err := http.NewRequest("GET", "", nil)
if err != nil {
t.Fatal(err)
}
// we must set the function name URL variable to pass the validation in proxyRequest
req = mux.SetURLVars(req, map[string]string{"name": "foo"})
proxyHandler.ServeHTTP(rr, req)
if rr.Code != tc {
t.Fatalf("unexpected status code; got: %d, expected: %d", rr.Code, tc)
}
if tc > 399 && strings.TrimSpace(rr.Body.String()) != "upstream error" {
t.Fatalf("unexpected response body, got: %s", rr.Body.String())
}
})
}
}
func Test_pathParsing(t *testing.T) {
tt := []struct {
name string
functionPath string
functionName string
extraPath string
statusCode int
}{
{
"simple_name_match",
"/function/echo",
"echo",
"",
200,
},
{
"simple_name_match_with_trailing_slash",
"/function/echo/",
"echo",
"",
200,
},
{
"name_match_with_additional_path_values",
"/function/echo/subPath/extras",
"echo",
"subPath/extras",
200,
},
{
"name_match_with_additional_path_values_and_querystring",
"/function/echo/subPath/extras?query=true",
"echo",
"subPath/extras",
200,
},
{
"not_found_if_no_name",
"/function/",
"",
"",
404,
},
}
// Need to create a router that we can pass the request through so that the vars will be added to the context
router := mux.NewRouter()
router.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", varHandler)
router.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", varHandler)
router.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/{params:.*}", varHandler)
for _, s := range tt {
t.Run(s.name, func(t *testing.T) {
rr := httptest.NewRecorder()
req, err := http.NewRequest("GET", s.functionPath, nil)
if err != nil {
t.Fatal(err)
}
router.ServeHTTP(rr, req)
if rr.Code != s.statusCode {
t.Fatalf("unexpected status code; got: %d, expected: %d", rr.Code, s.statusCode)
}
body := rr.Body.String()
expectedBody := fmt.Sprintf("name: %s params: %s", s.functionName, s.extraPath)
if s.statusCode == http.StatusOK && body != expectedBody {
t.Fatalf("incorrect function name and path params; got: %s, expected: %s", body, expectedBody)
}
})
}
}
func Test_buildProxyRequest_Body_Method_Query(t *testing.T) {
srcBytes := []byte("hello world")
reader := bytes.NewReader(srcBytes)
request, _ := http.NewRequest(http.MethodPost, "/?code=1", reader)
request.Header.Set("X-Source", "unit-test")
if request.URL.RawQuery != "code=1" {
t.Errorf("Query - want: %s, got: %s", "code=1", request.URL.RawQuery)
t.Fail()
}
funcURL, _ := testResolver("funcName")
upstream, err := buildProxyRequest(request, funcURL, "")
if err != nil {
t.Fatal(err.Error())
}
if request.Method != upstream.Method {
t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method)
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
t.Fail()
}
if request.Header.Get("X-Source") != upstream.Header.Get("X-Source") {
t.Errorf("Header X-Source - want: %s, got: %s", request.Header.Get("X-Source"), upstream.Header.Get("X-Source"))
t.Fail()
}
if request.URL.RawQuery != upstream.URL.RawQuery {
t.Errorf("URL.RawQuery - want: %s, got: %s", request.URL.RawQuery, upstream.URL.RawQuery)
t.Fail()
}
}
func Test_buildProxyRequest_NoBody_GetMethod_NoQuery(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/", nil)
funcURL, _ := testResolver("funcName")
upstream, err := buildProxyRequest(request, funcURL, "")
if err != nil {
t.Fatal(err.Error())
}
if request.Method != upstream.Method {
t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method)
t.Fail()
}
if upstream.Body != nil {
t.Errorf("Body - expected nil")
t.Fail()
}
if request.URL.RawQuery != upstream.URL.RawQuery {
t.Errorf("URL.RawQuery - want: %s, got: %s", request.URL.RawQuery, upstream.URL.RawQuery)
t.Fail()
}
}
func Test_buildProxyRequest_HasXForwardedHostHeaderWhenSet(t *testing.T) {
srcBytes := []byte("hello world")
reader := bytes.NewReader(srcBytes)
request, err := http.NewRequest(http.MethodPost, "http://gateway/function?code=1", reader)
if err != nil {
t.Fatal(err)
}
funcURL, _ := testResolver("funcName")
upstream, err := buildProxyRequest(request, funcURL, "/")
if err != nil {
t.Fatal(err.Error())
}
if request.Host != upstream.Header.Get("X-Forwarded-Host") {
t.Errorf("Host - want: %s, got: %s", request.Host, upstream.Header.Get("X-Forwarded-Host"))
}
}
func Test_buildProxyRequest_XForwardedHostHeader_Empty_WhenNotSet(t *testing.T) {
srcBytes := []byte("hello world")
reader := bytes.NewReader(srcBytes)
request, err := http.NewRequest(http.MethodPost, "/function", reader)
if err != nil {
t.Fatal(err)
}
funcURL, _ := testResolver("funcName")
upstream, err := buildProxyRequest(request, funcURL, "/")
if err != nil {
t.Fatal(err.Error())
}
if request.Host != upstream.Header.Get("X-Forwarded-Host") {
t.Errorf("Host - want: %s, got: %s", request.Host, upstream.Header.Get("X-Forwarded-Host"))
}
}
func Test_buildProxyRequest_XForwardedHostHeader_WhenAlreadyPresent(t *testing.T) {
srcBytes := []byte("hello world")
headerValue := "test.openfaas.com"
reader := bytes.NewReader(srcBytes)
request, err := http.NewRequest(http.MethodPost, "/function/test", reader)
if err != nil {
t.Fatal(err)
}
request.Header.Set("X-Forwarded-Host", headerValue)
funcURL, _ := testResolver("funcName")
upstream, err := buildProxyRequest(request, funcURL, "/")
if err != nil {
t.Fatal(err.Error())
}
if upstream.Header.Get("X-Forwarded-Host") != headerValue {
t.Errorf("X-Forwarded-Host - want: %s, got: %s", headerValue, upstream.Header.Get("X-Forwarded-Host"))
}
}
func Test_buildProxyRequest_WithPathNoQuery(t *testing.T) {
srcBytes := []byte("hello world")
functionPath := "/employee/info/300"
requestPath := fmt.Sprintf("/function/xyz%s", functionPath)
reader := bytes.NewReader(srcBytes)
request, _ := http.NewRequest(http.MethodPost, requestPath, reader)
request.Header.Set("X-Source", "unit-test")
queryWant := ""
if request.URL.RawQuery != queryWant {
t.Errorf("Query - want: %s, got: %s", queryWant, request.URL.RawQuery)
t.Fail()
}
funcURL, _ := testResolver("xyz")
upstream, err := buildProxyRequest(request, funcURL, functionPath)
if err != nil {
t.Fatal(err.Error())
}
if request.Method != upstream.Method {
t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method)
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
t.Fail()
}
if request.Header.Get("X-Source") != upstream.Header.Get("X-Source") {
t.Errorf("Header X-Source - want: %s, got: %s", request.Header.Get("X-Source"), upstream.Header.Get("X-Source"))
t.Fail()
}
if request.URL.RawQuery != upstream.URL.RawQuery {
t.Errorf("URL.RawQuery - want: %s, got: %s", request.URL.RawQuery, upstream.URL.RawQuery)
t.Fail()
}
if functionPath != upstream.URL.Path {
t.Errorf("URL.Path - want: %s, got: %s", functionPath, upstream.URL.Path)
t.Fail()
}
}
func Test_buildProxyRequest_WithNoPathNoQuery(t *testing.T) {
srcBytes := []byte("hello world")
functionPath := "/"
requestPath := fmt.Sprintf("/function/xyz%s", functionPath)
reader := bytes.NewReader(srcBytes)
request, _ := http.NewRequest(http.MethodPost, requestPath, reader)
request.Header.Set("X-Source", "unit-test")
queryWant := ""
if request.URL.RawQuery != queryWant {
t.Errorf("Query - want: %s, got: %s", queryWant, request.URL.RawQuery)
t.Fail()
}
funcURL, _ := testResolver("xyz")
upstream, err := buildProxyRequest(request, funcURL, functionPath)
if err != nil {
t.Fatal(err.Error())
}
if request.Method != upstream.Method {
t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method)
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
t.Fail()
}
if request.Header.Get("X-Source") != upstream.Header.Get("X-Source") {
t.Errorf("Header X-Source - want: %s, got: %s", request.Header.Get("X-Source"), upstream.Header.Get("X-Source"))
t.Fail()
}
if request.URL.RawQuery != upstream.URL.RawQuery {
t.Errorf("URL.RawQuery - want: %s, got: %s", request.URL.RawQuery, upstream.URL.RawQuery)
t.Fail()
}
if functionPath != upstream.URL.Path {
t.Errorf("URL.Path - want: %s, got: %s", functionPath, upstream.URL.Path)
t.Fail()
}
}
func Test_buildProxyRequest_WithPathAndQuery(t *testing.T) {
srcBytes := []byte("hello world")
functionPath := "/employee/info/300"
requestPath := fmt.Sprintf("/function/xyz%s?code=1", functionPath)
reader := bytes.NewReader(srcBytes)
request, _ := http.NewRequest(http.MethodPost, requestPath, reader)
request.Header.Set("X-Source", "unit-test")
if request.URL.RawQuery != "code=1" {
t.Errorf("Query - want: %s, got: %s", "code=1", request.URL.RawQuery)
t.Fail()
}
funcURL, _ := testResolver("xyz")
upstream, err := buildProxyRequest(request, funcURL, functionPath)
if err != nil {
t.Fatal(err.Error())
}
if request.Method != upstream.Method {
t.Errorf("Method - want: %s, got: %s", request.Method, upstream.Method)
t.Fail()
}
upstreamBytes, _ := ioutil.ReadAll(upstream.Body)
if string(upstreamBytes) != string(srcBytes) {
t.Errorf("Body - want: %s, got: %s", string(upstreamBytes), string(srcBytes))
t.Fail()
}
if request.Header.Get("X-Source") != upstream.Header.Get("X-Source") {
t.Errorf("Header X-Source - want: %s, got: %s", request.Header.Get("X-Source"), upstream.Header.Get("X-Source"))
t.Fail()
}
if request.URL.RawQuery != upstream.URL.RawQuery {
t.Errorf("URL.RawQuery - want: %s, got: %s", request.URL.RawQuery, upstream.URL.RawQuery)
t.Fail()
}
if functionPath != upstream.URL.Path {
t.Errorf("URL.Path - want: %s, got: %s", functionPath, upstream.URL.Path)
t.Fail()
}
}

View File

@ -1,91 +0,0 @@
// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package bootstrap
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/openfaas/faas-provider/auth"
"github.com/openfaas/faas-provider/types"
)
var r *mux.Router
// Mark this as a Golang "package"
func init() {
r = mux.NewRouter()
}
// Router gives access to the underlying router for when new routes need to be added.
func Router() *mux.Router {
return r
}
// Serve load your handlers into the correct OpenFaaS route spec. This function is blocking.
func Serve(handlers *types.FaaSHandlers, config *types.FaaSConfig) {
if config.EnableBasicAuth {
reader := auth.ReadBasicAuthFromDisk{
SecretMountPath: config.SecretMountPath,
}
credentials, err := reader.Read()
if err != nil {
log.Fatal(err)
}
handlers.FunctionReader = auth.DecorateWithBasicAuth(handlers.FunctionReader, credentials)
handlers.DeployHandler = auth.DecorateWithBasicAuth(handlers.DeployHandler, credentials)
handlers.DeleteHandler = auth.DecorateWithBasicAuth(handlers.DeleteHandler, credentials)
handlers.UpdateHandler = auth.DecorateWithBasicAuth(handlers.UpdateHandler, credentials)
handlers.ReplicaReader = auth.DecorateWithBasicAuth(handlers.ReplicaReader, credentials)
handlers.ReplicaUpdater = auth.DecorateWithBasicAuth(handlers.ReplicaUpdater, credentials)
handlers.InfoHandler = auth.DecorateWithBasicAuth(handlers.InfoHandler, credentials)
handlers.SecretHandler = auth.DecorateWithBasicAuth(handlers.SecretHandler, credentials)
handlers.LogHandler = auth.DecorateWithBasicAuth(handlers.LogHandler, credentials)
}
// System (auth) endpoints
r.HandleFunc("/system/functions", handlers.FunctionReader).Methods("GET")
r.HandleFunc("/system/functions", handlers.DeployHandler).Methods("POST")
r.HandleFunc("/system/functions", handlers.DeleteHandler).Methods("DELETE")
r.HandleFunc("/system/functions", handlers.UpdateHandler).Methods("PUT")
r.HandleFunc("/system/function/{name:[-a-zA-Z_0-9]+}", handlers.ReplicaReader).Methods("GET")
r.HandleFunc("/system/scale-function/{name:[-a-zA-Z_0-9]+}", handlers.ReplicaUpdater).Methods("POST")
r.HandleFunc("/system/info", handlers.InfoHandler).Methods("GET")
r.HandleFunc("/system/secrets", handlers.SecretHandler).Methods(http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete)
r.HandleFunc("/system/logs", handlers.LogHandler).Methods(http.MethodGet)
// Open endpoints
r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}", handlers.FunctionProxy)
r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/", handlers.FunctionProxy)
r.HandleFunc("/function/{name:[-a-zA-Z_0-9]+}/{params:.*}", handlers.FunctionProxy)
if config.EnableHealth {
r.HandleFunc("/healthz", handlers.HealthHandler).Methods("GET")
}
readTimeout := config.ReadTimeout
writeTimeout := config.WriteTimeout
tcpPort := 8080
if config.TCPPort != nil {
tcpPort = *config.TCPPort
}
s := &http.Server{
Addr: fmt.Sprintf(":%d", tcpPort),
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes, // 1MB - can be overridden by setting Server.MaxHeaderBytes.
Handler: r,
}
log.Fatal(s.ListenAndServe())
}

View File

@ -46,6 +46,9 @@ type FunctionDeployment struct {
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem"`
// Namespace for the function to be deployed into
Namespace string `json:"namespace,omitempty"`
}
// FunctionResources Memory and CPU
@ -72,7 +75,8 @@ type FunctionStatus struct {
// EnvProcess is the process to pass to the watchdog, if in use
EnvProcess string `json:"envProcess"`
// AvailableReplicas is the count of replicas ready to receive invocations as reported by the backend
// AvailableReplicas is the count of replicas ready to receive
// invocations as reported by the backend
AvailableReplicas uint64 `json:"availableReplicas"`
// Labels are metadata for functions which may be used by the
@ -82,4 +86,13 @@ type FunctionStatus struct {
// Annotations are metadata for functions which may be used by the
// backend for management, orchestration, events and build tasks
Annotations *map[string]string `json:"annotations"`
// Namespace where the function can be accessed
Namespace string `json:"namespace,omitempty"`
}
// Secret for underlying orchestrator
type Secret struct {
Name string `json:"name"`
Value string `json:"value,omitempty"`
}