diff --git a/Makefile b/Makefile index 37e41c742..6dfc23c9c 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ build-docker: test: go test -v $(shell glide nv | grep -v examples | grep -v tool | grep -v fnctl) + cd fnctl && $(MAKE) test test-docker: docker run -ti --privileged --rm -e LOG_LEVEL=debug \ diff --git a/api/server/runner.go b/api/server/runner.go index 2395e9ad0..766a0b545 100644 --- a/api/server/runner.go +++ b/api/server/runner.go @@ -30,7 +30,7 @@ func handleSpecial(c *gin.Context) { } } -func toEnvName(envtype, name string) string { +func ToEnvName(envtype, name string) string { name = strings.ToUpper(strings.Replace(name, "-", "_", -1)) return fmt.Sprintf("%s_%s", envtype, name) } @@ -128,22 +128,22 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) { // app config for k, v := range app.Config { - envVars[toEnvName("CONFIG", k)] = v + envVars[ToEnvName("CONFIG", k)] = v } // route config for k, v := range found.Config { - envVars[toEnvName("CONFIG", k)] = v + envVars[ToEnvName("CONFIG", k)] = v } // params for _, param := range params { - envVars[toEnvName("PARAM", param.Key)] = param.Value + envVars[ToEnvName("PARAM", param.Key)] = param.Value } // headers for header, value := range c.Request.Header { - envVars[toEnvName("HEADER", header)] = strings.Join(value, " ") + envVars[ToEnvName("HEADER", header)] = strings.Join(value, " ") } cfg := &runner.Config{ diff --git a/fnctl/Makefile b/fnctl/Makefile index 570a4f2ff..6fb780ec9 100644 --- a/fnctl/Makefile +++ b/fnctl/Makefile @@ -9,3 +9,6 @@ docker: vendor vendor: go get -u . + +test: + go test -v $(shell glide nv) \ No newline at end of file diff --git a/fnctl/main.go b/fnctl/main.go index 217055928..69e9f6c1a 100644 --- a/fnctl/main.go +++ b/fnctl/main.go @@ -21,6 +21,7 @@ func main() { apps(), build(), bump(), + call(), lambda(), publish(), routes(), diff --git a/fnctl/routes.go b/fnctl/routes.go index b0343bac1..df4a249e3 100644 --- a/fnctl/routes.go +++ b/fnctl/routes.go @@ -8,6 +8,7 @@ import ( "net/url" "os" "path" + "strings" "text/tabwriter" "github.com/iron-io/functions_go" @@ -22,18 +23,20 @@ type routesCmd struct { func routes() cli.Command { r := routesCmd{RoutesApi: functions.NewRoutesApi()} + flags := append(confFlags(&r.Configuration), []cli.Flag{}...) return cli.Command{ Name: "routes", Usage: "list routes", ArgsUsage: "fnclt routes", - Flags: append(confFlags(&r.Configuration), []cli.Flag{}...), + Flags: flags, Action: r.list, Subcommands: []cli.Command{ { - Name: "run", - Usage: "run a route", + Name: "call", + Usage: "call a route", ArgsUsage: "appName /path", - Action: r.run, + Action: r.call, + Flags: append(flags, runflags()...), }, { Name: "create", @@ -51,6 +54,20 @@ func routes() cli.Command { } } +func call() cli.Command { + r := routesCmd{RoutesApi: functions.NewRoutesApi()} + + flags := append([]cli.Flag{}, confFlags(&r.Configuration)...) + flags = append(flags, runflags()...) + return cli.Command{ + Name: "call", + Usage: "call a remote function", + ArgsUsage: "appName /path", + Flags: flags, + Action: r.call, + } +} + func (a *routesCmd) list(c *cli.Context) error { if c.Args().First() == "" { return errors.New("error: routes listing takes one argument, an app name") @@ -85,7 +102,7 @@ func (a *routesCmd) list(c *cli.Context) error { return nil } -func (a *routesCmd) run(c *cli.Context) error { +func (a *routesCmd) call(c *cli.Context) error { if c.Args().Get(0) == "" || c.Args().Get(1) == "" { return errors.New("error: routes listing takes three arguments: an app name and a route") } @@ -108,7 +125,15 @@ func (a *routesCmd) run(c *cli.Context) error { content = os.Stdin } - resp, err := http.Post(baseURL.ResolveReference(u).String(), "application/json", content) + req, err := http.NewRequest("POST", baseURL.ResolveReference(u).String(), content) + if err != nil { + return fmt.Errorf("error running route: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + envAsHeader(req, c.StringSlice("e")) + + resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("error running route: %v", err) } @@ -117,6 +142,19 @@ func (a *routesCmd) run(c *cli.Context) error { return nil } +func envAsHeader(req *http.Request, selectedEnv []string) { + detectedEnv := os.Environ() + if len(selectedEnv) > 0 { + detectedEnv = selectedEnv + } + + for _, e := range detectedEnv { + kv := strings.Split(e, "=") + name := kv[0] + req.Header.Set(name, os.Getenv(name)) + } +} + func (a *routesCmd) create(c *cli.Context) error { if c.Args().Get(0) == "" || c.Args().Get(1) == "" || c.Args().Get(2) == "" { return errors.New("error: routes listing takes three arguments: an app name, a route path and an image") diff --git a/fnctl/run.go b/fnctl/run.go index 0b5fdb21e..0cf6c6e5d 100644 --- a/fnctl/run.go +++ b/fnctl/run.go @@ -1,18 +1,82 @@ package main import ( - "github.com/iron-io/functions_go" + "errors" + "fmt" + "os" + "os/exec" + "strings" + "github.com/urfave/cli" ) func run() cli.Command { - r := routesCmd{RoutesApi: functions.NewRoutesApi()} + r := runCmd{} return cli.Command{ Name: "run", - Usage: "run function", - ArgsUsage: "fnclt run appName /path", - Flags: append(confFlags(&r.Configuration), []cli.Flag{}...), + Usage: "run a function locally", + ArgsUsage: "USERNAME/image:tag", + Flags: append(runflags(), []cli.Flag{}...), Action: r.run, } } + +type runCmd struct{} + +func runflags() []cli.Flag { + return []cli.Flag{ + cli.StringSliceFlag{ + Name: "e", + Usage: "limit the environment variables sent to function, if ommited then all are sent.", + }, + } +} + +func (r *runCmd) run(c *cli.Context) error { + image := c.Args().First() + if image == "" { + return errors.New("error: image name is missing") + } + + sh := []string{"docker", "run", "--rm", "-i"} + + var env []string + detectedEnv := os.Environ() + if se := c.StringSlice("e"); len(se) > 0 { + detectedEnv = se + } + + for _, e := range detectedEnv { + shellvar, envvar := extractEnvVar(e) + sh = append(sh, shellvar...) + env = append(env, envvar) + } + + dockerenv := []string{"DOCKER_TLS_VERIFY", "DOCKER_HOST", "DOCKER_CERT_PATH", "DOCKER_MACHINE_NAME"} + for _, e := range dockerenv { + env = append(env, fmt.Sprint(e, "=", os.Getenv(e))) + } + + sh = append(sh, image) + cmd := exec.Command(sh[0], sh[1:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = env + return cmd.Run() +} + +func extractEnvVar(e string) ([]string, string) { + kv := strings.Split(e, "=") + name := toEnvName("HEADER", kv[0]) + sh := []string{"-e", name} + env := fmt.Sprintf("%s=%s", name, os.Getenv(kv[0])) + return sh, env +} + +// From server.toEnvName() +func toEnvName(envtype, name string) string { + name = strings.ToUpper(strings.Replace(name, "-", "_", -1)) + return fmt.Sprintf("%s_%s", envtype, name) +}