Merge pull request #299 from fnproject/fn-the-things

FN_ prefix env vars
This commit is contained in:
Reed Allman
2017-09-11 11:33:43 -07:00
committed by GitHub
17 changed files with 376 additions and 52 deletions

View File

@@ -30,9 +30,7 @@ import (
// TODO handle timeouts / no response in sync & async (sync is json+503 atm, not 504, async is empty log+status) // TODO handle timeouts / no response in sync & async (sync is json+503 atm, not 504, async is empty log+status)
// see also: server/runner.go wrapping the response writer there, but need to handle async too (push down?) // see also: server/runner.go wrapping the response writer there, but need to handle async too (push down?)
// TODO herd launch prevention part deux // TODO herd launch prevention part deux
// TODO plumb FXLB-WAIT back - can we use headers now? maybe let's use api // TODO storing logs / call can push call over the timeout
// TODO none of the Datastore methods actually use the ctx for timeouts :(
// TODO not adding padding if call times out to store appropriately (ctx timed out, happenstance it works now cuz of ^)
// TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id) // TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id)
// TODO limit the request body length when making calls // TODO limit the request body length when making calls
// TODO discuss concrete policy for hot launch or timeout / timeout vs time left // TODO discuss concrete policy for hot launch or timeout / timeout vs time left
@@ -50,7 +48,6 @@ import (
// dies). need coordination w/ db. // dies). need coordination w/ db.
// TODO if a cold call times out but container is created but hasn't replied, could // TODO if a cold call times out but container is created but hasn't replied, could
// end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?) // end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?)
// TODO we should prob not be logging all async output to the logs by default...
// TODO the call api should fill in all the fields // TODO the call api should fill in all the fields
// TODO the log api should be plaintext (or at least offer it) // TODO the log api should be plaintext (or at least offer it)
// TODO func logger needs to be hanged, dragged and quartered. in reverse order. // TODO func logger needs to be hanged, dragged and quartered. in reverse order.

275
api/agent/agent_test.go Normal file
View File

@@ -0,0 +1,275 @@
package agent
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/fnproject/fn/api/datastore"
"github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/api/mqs"
)
func TestCallConfigurationRequest(t *testing.T) {
appName := "myapp"
path := "/sleeper"
image := "fnproject/sleeper"
const timeout = 1
const idleTimeout = 20
const memory = 256
typ := "sync"
format := "default"
cfg := models.Config{"APP_VAR": "FOO"}
rCfg := models.Config{"ROUTE_VAR": "BAR"}
ds := datastore.NewMockInit(
[]*models.App{
{Name: appName, Config: cfg},
},
[]*models.Route{
{
Config: rCfg,
Path: path,
AppName: appName,
Image: image,
Type: typ,
Format: format,
Timeout: timeout,
IdleTimeout: idleTimeout,
Memory: memory,
},
}, nil,
)
a := New(ds, new(mqs.Mock))
defer a.Close()
w := httptest.NewRecorder()
method := "GET"
url := "http://127.0.0.1:8080/r/" + appName + path
payload := "payload"
contentLength := strconv.Itoa(len(payload))
req, err := http.NewRequest(method, url, strings.NewReader(payload))
if err != nil {
t.Fatal(err)
}
req.Header.Add("MYREALHEADER", "FOOLORD")
req.Header.Add("MYREALHEADER", "FOOPEASANT")
req.Header.Add("Content-Length", contentLength)
req.Header.Add("FN_ROUTE", "thewrongroute") // ensures that this doesn't leak out, should be overwritten
call, err := a.GetCall(
WithWriter(w), // XXX (reed): order matters [for now]
FromRequest(appName, path, req),
)
if err != nil {
t.Fatal(err)
}
model := call.Model()
// make sure the values are all set correctly
if model.ID == "" {
t.Fatal("model does not have id, GetCall should assign id")
}
if model.AppName != appName {
t.Fatal("app name mismatch", model.AppName, appName)
}
if model.Path != path {
t.Fatal("path mismatch", model.Path, path)
}
if model.Image != image {
t.Fatal("image mismatch", model.Image, image)
}
if model.Type != "sync" {
t.Fatal("route type mismatch", model.Type)
}
if model.Priority == nil {
t.Fatal("GetCall should make priority non-nil so that async works because for whatever reason some clowns plumbed it all over the mqs even though the user can't specify it gg")
}
if model.Timeout != timeout {
t.Fatal("timeout mismatch", model.Timeout, timeout)
}
if model.IdleTimeout != idleTimeout {
t.Fatal("idle timeout mismatch", model.IdleTimeout, idleTimeout)
}
if time.Time(model.CreatedAt).IsZero() {
t.Fatal("GetCall should stamp CreatedAt, got nil timestamp")
}
if model.URL != url {
t.Fatal("url mismatch", model.URL, url)
}
if model.Method != method {
t.Fatal("method mismatch", model.Method, method)
}
if model.Payload != "" { // NOTE: this is expected atm
t.Fatal("GetCall FromRequest should not fill payload, got non-empty payload", model.Payload)
}
expectedBase := map[string]string{
"FN_FORMAT": format,
"FN_APP_NAME": appName,
"FN_ROUTE": path,
"FN_MEMORY": strconv.Itoa(memory),
"FN_TYPE": typ,
"APP_VAR": "FOO",
"ROUTE_VAR": "BAR",
}
expectedEnv := make(map[string]string)
for k, v := range expectedBase {
expectedEnv[k] = v
}
for k, v := range expectedBase {
if v2 := model.BaseEnv[k]; v2 != v {
t.Fatal("base var mismatch", k, v, v2, model.BaseEnv)
}
delete(expectedBase, k)
}
if len(expectedBase) > 0 {
t.Fatal("got extra vars in base env set, add me to tests ;)", expectedBase)
}
expectedEnv["FN_CALL_ID"] = model.ID
expectedEnv["FN_METHOD"] = method
expectedEnv["FN_REQUEST_URL"] = url
// do this before the "real" headers get sucked in cuz they are formatted differently
expectedHeaders := make(http.Header)
for k, v := range expectedEnv {
expectedHeaders.Add(k, v)
}
// from the request headers (look different in env than in req.Header, idk, up to user anger)
// req headers down cases things
expectedEnv["FN_HEADER_Myrealheader"] = "FOOLORD, FOOPEASANT"
expectedEnv["FN_HEADER_Content_Length"] = contentLength
for k, v := range expectedEnv {
if v2 := model.EnvVars[k]; v2 != v {
t.Fatal("env var mismatch", k, v, v2, model.EnvVars)
}
delete(expectedEnv, k)
}
if len(expectedEnv) > 0 {
t.Fatal("got extra vars in base env set, add me to tests ;)", expectedBase)
}
expectedHeaders.Add("MYREALHEADER", "FOOLORD")
expectedHeaders.Add("MYREALHEADER", "FOOPEASANT")
expectedHeaders.Add("Content-Length", contentLength)
for k, vs := range req.Header {
for i, v := range expectedHeaders[k] {
if i >= len(vs) || vs[i] != v {
t.Fatal("header mismatch", k, vs)
}
}
delete(expectedHeaders, k)
}
if len(expectedHeaders) > 0 {
t.Fatal("got extra headers, bad")
}
if w.Header()["Fn_call_id"][0] != model.ID {
t.Fatal("response writer should have the call id, or else")
}
// TODO check response writer for route headers
// TODO idk what param even is or how to get them, but need to test those
// TODO we should double check the things we're rewriting defaults of, like type, format, timeout, idle_timeout
}
func TestCallConfigurationModel(t *testing.T) {
appName := "myapp"
path := "/sleeper"
image := "fnproject/sleeper"
const timeout = 1
const idleTimeout = 20
const memory = 256
method := "GET"
url := "http://127.0.0.1:8080/r/" + appName + path
payload := "payload"
typ := "sync"
format := "default"
env := map[string]string{
"FN_FORMAT": format,
"FN_APP_NAME": appName,
"FN_ROUTE": path,
"FN_MEMORY": strconv.Itoa(memory),
"FN_TYPE": typ,
"APP_VAR": "FOO",
"ROUTE_VAR": "BAR",
"DOUBLE_VAR": "BIZ, BAZ",
}
cm := &models.Call{
BaseEnv: env,
EnvVars: env,
AppName: appName,
Path: path,
Image: image,
Type: typ,
Format: format,
Timeout: timeout,
IdleTimeout: idleTimeout,
Memory: memory,
Payload: payload,
URL: url,
Method: method,
}
// FromModel doesn't need a datastore, for now...
ds := datastore.NewMockInit(nil, nil, nil)
a := New(ds, new(mqs.Mock))
defer a.Close()
callI, err := a.GetCall(FromModel(cm))
if err != nil {
t.Fatal(err)
}
// make sure headers seem reasonable
req := callI.(*call).req
// NOTE these are added as is atm, and if the env vars were comma joined
// they are not again here comma separated.
expectedHeaders := make(http.Header)
for k, v := range env {
expectedHeaders.Add(k, v)
}
for k, vs := range req.Header {
for i, v := range expectedHeaders[k] {
if i >= len(vs) || vs[i] != v {
t.Fatal("header mismatch", k, vs)
}
}
delete(expectedHeaders, k)
}
if len(expectedHeaders) > 0 {
t.Fatal("got extra headers, bad")
}
var b bytes.Buffer
io.Copy(&b, req.Body)
if b.String() != payload {
t.Fatal("expected payload to match, but it was a lie")
}
}

View File

@@ -65,12 +65,8 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
// baseVars are the vars on the route & app, not on this specific request [for hot functions] // baseVars are the vars on the route & app, not on this specific request [for hot functions]
baseVars := make(map[string]string, len(app.Config)+len(route.Config)+3) baseVars := make(map[string]string, len(app.Config)+len(route.Config)+3)
baseVars["FN_FORMAT"] = route.Format
baseVars["APP_NAME"] = appName
baseVars["ROUTE"] = route.Path
baseVars["MEMORY_MB"] = fmt.Sprintf("%d", route.Memory)
// app config // add app & route config before our standard additions
for k, v := range app.Config { for k, v := range app.Config {
k = toEnvName("", k) k = toEnvName("", k)
baseVars[k] = v baseVars[k] = v
@@ -80,6 +76,12 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
baseVars[k] = v baseVars[k] = v
} }
baseVars["FN_FORMAT"] = route.Format
baseVars["FN_APP_NAME"] = appName
baseVars["FN_ROUTE"] = route.Path
baseVars["FN_MEMORY"] = fmt.Sprintf("%d", route.Memory)
baseVars["FN_TYPE"] = route.Type
// envVars contains the full set of env vars, per request + base // envVars contains the full set of env vars, per request + base
envVars := make(map[string]string, len(baseVars)+len(params)+len(req.Header)+3) envVars := make(map[string]string, len(baseVars)+len(params)+len(req.Header)+3)
@@ -87,30 +89,43 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
envVars[k] = v envVars[k] = v
} }
envVars["CALL_ID"] = id envVars["FN_CALL_ID"] = id
envVars["METHOD"] = req.Method envVars["FN_METHOD"] = req.Method
envVars["REQUEST_URL"] = fmt.Sprintf("%v://%v%v", func() string { envVars["FN_REQUEST_URL"] = func() string {
if req.TLS == nil { if req.URL.Scheme == "" {
return "http" if req.TLS == nil {
req.URL.Scheme = "http"
} else {
req.URL.Scheme = "https"
}
} }
return "https" if req.URL.Host == "" {
}(), req.Host, req.URL.String()) req.URL.Host = req.Host
}
return req.URL.String()
}()
// params // params
for _, param := range params { for _, param := range params {
envVars[toEnvName("PARAM", param.Key)] = param.Value envVars[toEnvName("FN_PARAM", param.Key)] = param.Value
} }
headerVars := make(map[string]string, len(req.Header)) headerVars := make(map[string]string, len(req.Header))
for k, v := range req.Header { for k, v := range req.Header {
headerVars[toEnvName("HEADER", k)] = strings.Join(v, ", ") if !noOverrideVars(k) { // NOTE if we don't do this, they'll leak in (don't want people relying on this behavior)
headerVars[toEnvName("FN_HEADER", k)] = strings.Join(v, ", ")
}
} }
// add all the env vars we build to the request headers // add all the env vars we build to the request headers
// TODO should we save req.Headers and copy OVER app.Config / route.Config ?
for k, v := range envVars { for k, v := range envVars {
req.Header.Add(k, v) if noOverrideVars(k) {
// overwrite the passed in request headers explicitly with the generated ones
req.Header.Set(k, v)
} else {
req.Header.Add(k, v)
}
} }
for k, v := range headerVars { for k, v := range headerVars {
@@ -118,6 +133,7 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
} }
// TODO this relies on ordering of opts, but tests make sure it works, probably re-plumb/destroy headers // TODO this relies on ordering of opts, but tests make sure it works, probably re-plumb/destroy headers
// TODO async should probably supply an http.ResponseWriter that records the logs, to attach response headers to
if rw, ok := c.w.(http.ResponseWriter); ok { if rw, ok := c.w.(http.ResponseWriter); ok {
rw.Header().Add("FN_CALL_ID", id) rw.Header().Add("FN_CALL_ID", id)
for k, vs := range route.Headers { for k, vs := range route.Headers {
@@ -161,6 +177,27 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
} }
} }
func noOverrideVars(key string) bool {
// descrepency in casing b/w req headers and env vars, force matches
return overrideVars[strings.ToUpper(key)]
}
// overrideVars means that the app config, route config or header vars
// must not overwrite the generated values in call construction.
var overrideVars = map[string]bool{
"FN_FORMAT": true,
"FN_APP_NAME": true,
"FN_ROUTE": true,
"FN_MEMORY": true,
"FN_TYPE": true,
"FN_CALL_ID": true,
"FN_METHOD": true,
"FN_REQUEST_URL": true,
}
// TODO this currently relies on FromRequest having happened before to create the model
// here, to be a fully qualified model. We probably should double check but having a way
// to bypass will likely be what's used anyway unless forced.
func FromModel(mCall *models.Call) CallOpt { func FromModel(mCall *models.Call) CallOpt {
return func(a *agent, c *call) error { return func(a *agent, c *call) error {
c.Call = mCall c.Call = mCall
@@ -246,6 +283,10 @@ func (c *call) Start(ctx context.Context) error {
c.StartedAt = strfmt.DateTime(time.Now()) c.StartedAt = strfmt.DateTime(time.Now())
c.Status = "running" c.Status = "running"
if rw, ok := c.w.(http.ResponseWriter); ok { // TODO need to figure out better way to wire response headers in
rw.Header().Set("XXX-FXLB-WAIT", time.Time(c.StartedAt).Sub(time.Time(c.CreatedAt)).String())
}
if c.Type == models.TypeAsync { if c.Type == models.TypeAsync {
// XXX (reed): make sure MQ reservation is lengthy. to skirt MQ semantics, // XXX (reed): make sure MQ reservation is lengthy. to skirt MQ semantics,
// we could add a new message to MQ w/ delay of call.Timeout and delete the // we could add a new message to MQ w/ delay of call.Timeout and delete the

View File

@@ -41,8 +41,10 @@ func (h *HTTPProtocol) Dispatch(w io.Writer, req *http.Request) error {
return err return err
} }
for k, v := range res.Header { for k, vs := range res.Header {
rw.Header()[k] = v for _, v := range vs {
rw.Header().Add(k, v) // on top of any specified on the route
}
} }
rw.WriteHeader(res.StatusCode) rw.WriteHeader(res.StatusCode)
// TODO should we TCP_CORK ? // TODO should we TCP_CORK ?

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"path" "path"
"strings" "strings"
"time"
"github.com/fnproject/fn/api" "github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/agent" "github.com/fnproject/fn/api/agent"
@@ -58,8 +59,9 @@ func (s *Server) serve(c *gin.Context, appName, path string) {
} }
// TODO we could add FireBeforeDispatch right here with Call in hand // TODO we could add FireBeforeDispatch right here with Call in hand
model := call.Model()
if model := call.Model(); model.Type == "async" { if model.Type == "async" {
// TODO we should push this into GetCall somehow (CallOpt maybe) or maybe agent.Queue(Call) ? // TODO we should push this into GetCall somehow (CallOpt maybe) or maybe agent.Queue(Call) ?
buf := bytes.NewBuffer(make([]byte, 0, c.Request.ContentLength)) // TODO sync.Pool me buf := bytes.NewBuffer(make([]byte, 0, c.Request.ContentLength)) // TODO sync.Pool me
_, err := buf.ReadFrom(c.Request.Body) _, err := buf.ReadFrom(c.Request.Body)
@@ -86,6 +88,10 @@ func (s *Server) serve(c *gin.Context, appName, path string) {
// we could filter that error out here too as right now it yells a little // we could filter that error out here too as right now it yells a little
if err == context.DeadlineExceeded { if err == context.DeadlineExceeded {
// TODO maneuver
// add this, since it means that start may not have been called [and it's relevant]
c.Writer.Header().Add("XXX-FXLB-WAIT", time.Now().Sub(time.Time(model.CreatedAt)).String())
err = models.ErrCallTimeout // 504 w/ friendly note err = models.ErrCallTimeout // 504 w/ friendly note
} }
// NOTE: if the task wrote the headers already then this will fail to write // NOTE: if the task wrote the headers already then this will fail to write

View File

@@ -31,15 +31,18 @@ To read in the function body, just read from STDIN.
You will also have access to a set of environment variables. You will also have access to a set of environment variables.
* REQUEST_URL - the full URL for the request ([parsing example](https://github.com/fnproject/fn/tree/master/examples/tutorial/params)) * `FN_REQUEST_URL` - the full URL for the request ([parsing example](https://github.com/fnproject/fn/tree/master/examples/tutorial/params))
* APP_NAME - the name of the application that matched this route, eg: `myapp` * `FN_APP_NAME` - the name of the application that matched this route, eg: `myapp`
* ROUTE - the matched route, eg: `/hello` * `FN_ROUTE` - the matched route, eg: `/hello`
* METHOD - the HTTP method for the request, eg: `GET` or `POST` * `FN_METHOD` - the HTTP method for the request, eg: `GET` or `POST`
* CALL_ID - a unique ID for each function execution. * `FN_CALL_ID` - a unique ID for each function execution.
* FORMAT - a string representing one of the [function formats](function-format.md), currently either `default` or `http`. Default is `default`. * `FN_FORMAT` - a string representing one of the [function formats](function-format.md), currently either `default` or `http`. Default is `default`.
* HEADER_X - the HTTP headers that were set for this request. Replace X with the upper cased name of the header and replace dashes in the header with underscores. * `FN_MEMORY` - a number representing the amount of memory available to the call, in MB
* X - any [configuration values](https://gitlab.oracledx.com/odx/functions/blob/master/fn/README.md#application-level-configuration) you've set * `FN_TYPE` - the type for this call, currently 'sync' or 'async'
* `FN_HEADER_$X` - the HTTP headers that were set for this request. Replace $X with the upper cased name of the header and replace dashes in the header with underscores.
* `$X` - any [configuration values](https://gitlab.oracledx.com/odx/functions/blob/master/fn/README.md#application-level-configuration) you've set
for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: `minio_secret=secret` will be exposed via MINIO_SECRET env var. for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: `minio_secret=secret` will be exposed via MINIO_SECRET env var.
* `FN_PARAM_$Y` - any variables found from parsing the URL. Replace $Y with any `:var` from the url.
Warning: these may change before release. Warning: these may change before release.

View File

@@ -13,7 +13,7 @@ import (
var noAuth = map[string]interface{}{} var noAuth = map[string]interface{}{}
func main() { func main() {
request := fmt.Sprintf("%s %s", os.Getenv("METHOD"), os.Getenv("ROUTE")) request := fmt.Sprintf("%s %s", os.Getenv("FN_METHOD"), os.Getenv("FN_ROUTE"))
dbURI := os.Getenv("DB") dbURI := os.Getenv("DB")
if dbURI == "" { if dbURI == "" {
@@ -36,7 +36,7 @@ func main() {
} }
// GETTING TOKEN // GETTING TOKEN
if os.Getenv("ROUTE") == "/token" { if os.Getenv("FN_ROUTE") == "/token" {
route.HandleToken(db) route.HandleToken(db)
return return
} }

View File

@@ -7,7 +7,7 @@ import (
) )
func HandlePostRead(db *database.Database, auth map[string]interface{}) { func HandlePostRead(db *database.Database, auth map[string]interface{}) {
id := os.Getenv("PARAM_ID") id := os.Getenv("FN_PARAM_ID")
if id == "" { if id == "" {
SendError("Missing post ID") SendError("Missing post ID")

View File

@@ -63,7 +63,7 @@ func HandleToken(db *database.Database) {
} }
func Authentication() (map[string]interface{}, bool) { func Authentication() (map[string]interface{}, bool) {
authorization := os.Getenv("HEADER_AUTHORIZATION") authorization := os.Getenv("FN_HEADER_AUTHORIZATION")
p := strings.Split(authorization, " ") p := strings.Split(authorization, " ")
if len(p) <= 1 { if len(p) <= 1 {

View File

@@ -9,8 +9,8 @@ docker rm test-mongo-func
docker run -p 27017:27017 --name test-mongo-func -d mongo docker run -p 27017:27017 --name test-mongo-func -d mongo
echo '{ "title": "My New Post", "body": "Hello world!", "user": "test" }' | docker run --rm -i -e METHOD=POST -e ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog echo '{ "title": "My New Post", "body": "Hello world!", "user": "test" }' | docker run --rm -i -e FN_METHOD=POST -e FN_ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
docker run --rm -i -e METHOD=GET -e ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog docker run --rm -i -e FN_METHOD=GET -e FN_ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
docker stop test-mongo-func docker stop test-mongo-func
docker rm test-mongo-func docker rm test-mongo-func

View File

@@ -21,21 +21,21 @@ if payload != ""
end end
# Also check for expected env vars: https://gitlab.oracledx.com/odx/functions/blob/master/docs/writing.md#inputs # Also check for expected env vars: https://gitlab.oracledx.com/odx/functions/blob/master/docs/writing.md#inputs
e = ENV["REQUEST_URL"] e = ENV["FN_REQUEST_URL"]
puts e puts e
uri = URI.parse(e) uri = URI.parse(e)
if !uri.scheme.start_with?('http') if !uri.scheme.start_with?('http')
raise "invalid REQUEST_URL, does not start with http" raise "invalid REQUEST_URL, does not start with http"
end end
e = ENV["METHOD"] e = ENV["FN_METHOD"]
if !(e == "GET" || e == "POST" || e == "DELETE" || e == "PATCH" || e == "PUT") if !(e == "GET" || e == "POST" || e == "DELETE" || e == "PATCH" || e == "PUT")
raise "Invalid METHOD: #{e}" raise "Invalid METHOD: #{e}"
end end
e = ENV["APP_NAME"] e = ENV["FN_APP_NAME"]
if e == nil || e == '' if e == nil || e == ''
raise "No APP_NAME found" raise "No APP_NAME found"
end end
e = ENV["ROUTE"] e = ENV["FN_ROUTE"]
if e == nil || e == '' if e == nil || e == ''
raise "No ROUTE found" raise "No ROUTE found"
end end

View File

@@ -16,11 +16,11 @@ import (
var ( var (
// command to execute, 'SELECT' or 'INSERT' // command to execute, 'SELECT' or 'INSERT'
command = os.Getenv("HEADER_COMMAND") command = os.Getenv("FN_HEADER_COMMAND")
// postgres host:port, e.g. 'postgres:5432' // postgres host:port, e.g. 'postgres:5432'
server = os.Getenv("HEADER_SERVER") server = os.Getenv("FN_HEADER_SERVER")
// postgres table name // postgres table name
table = os.Getenv("HEADER_TABLE") table = os.Getenv("FN_HEADER_TABLE")
) )
func main() { func main() {

View File

@@ -6,9 +6,9 @@ import (
) )
func main() { func main() {
envvar := os.Getenv("HEADER_ENVVAR") envvar := os.Getenv("FN_HEADER_ENVVAR")
if envvar != "" { if envvar != "" {
fmt.Println("HEADER_ENVVAR:", envvar) fmt.Println("FN_HEADER_ENVVAR:", envvar)
} }
fmt.Println("hw") fmt.Println("hw")
} }

View File

@@ -9,7 +9,7 @@ tests:
hw hw
- name: envvar - name: envvar
out: | out: |
HEADER_ENVVAR: trololo FN_HEADER_ENVVAR: trololo
hw hw
env: env:
envvar: trololo envvar: trololo

View File

@@ -9,7 +9,7 @@ tests:
hw hw
- name: envvar - name: envvar
out: | out: |
HEADER_ENVVAR: trololo FN_HEADER_ENVVAR: trololo
hw hw
env: env:
envvar: trololo envvar: trololo

View File

@@ -8,9 +8,9 @@ import (
) )
func main() { func main() {
s := os.Getenv("REQUEST_URL") s := os.Getenv("FN_REQUEST_URL")
fmt.Printf("REQUEST_URL --> %v\n\n", s) fmt.Printf("FN_REQUEST_URL --> %v\n\n", s)
u, err := url.Parse(s) u, err := url.Parse(s)
if err != nil { if err != nil {

View File

@@ -30,7 +30,7 @@ func main() {
numLoops := 1 numLoops := 1
// Parse the query string // Parse the query string
s := strings.Split(os.Getenv("REQUEST_URL"), "?") s := strings.Split(os.Getenv("FN_REQUEST_URL"), "?")
if len(s) > 1 { if len(s) > 1 {
for _, pair := range strings.Split(s[1], "&") { for _, pair := range strings.Split(s[1], "&") {
kv := strings.Split(pair, "=") kv := strings.Split(pair, "=")