Updates to fnctl to make UX better (#272)

* See the hello/go README for how this all works now.

* Node support for fnctl auto build

* Updated based on PR comments.
This commit is contained in:
Travis Reeder
2016-11-14 10:10:29 -08:00
committed by GitHub
parent 28d57e50a4
commit 3357476583
24 changed files with 402 additions and 185 deletions

View File

@@ -3,3 +3,5 @@ vendor/
/go /go
/app /app
/__uberscript__ /__uberscript__
function.yaml

View File

@@ -1,6 +1,4 @@
FROM iron/go FROM iron/go
WORKDIR /function
WORKDIR /app ADD . /function/
ADD . /app ENTRYPOINT ["./func"]
ENTRYPOINT ["./hello"]

View File

@@ -1,40 +1,24 @@
## Quick Example for a Go Function (3 minutes) # Quick Example for a Go Function (3 minutes)
This example will show you how to test and deploy Go (Golang) code to IronFunctions. This example will show you how to test and deploy Go (Golang) code to IronFunctions.
### 1. Prepare the `functions.yaml` file:
At functions.yaml you will find:
```yml
app: goapp
route: /hello
image: USERNAME/hello:0.0.1
build:
- docker run --rm -v "$PWD":/go/src/ -w /go/src/ -e "GOPATH=/go/src/vendor:/go" iron/go:dev go build -o hello
```
The important step here is to ensure you replace `USERNAME` with your Docker Hub account name. Some points of note:
the application name is `goapp` and the route for incoming requests is `/hello`. These informations are relevant for
the moment you try to test this function.
### 2. Build:
```sh ```sh
fnctl publish fnctl init <YOUR_DOCKERHUB_USERNAME>/hello
fnctl build
# test it
cat hello.payload.json | fnctl run
# push it to Docker Hub
fnctl push
# Create a route to this function on IronFunctions
fnctl routes create myapp /hello YOUR_DOCKERHUB_USERNAME/hello:0.0.X
# todo: Image name could be optional if we read the function file for creating the route. Then command could be:
fnctl routes create myapp /hello
``` ```
`-v` is optional, but it allows you to see how this function is being built. Now you use your function on IronFunctions:
### 3. Queue jobs for your function ```sh
curl -H "Content-Type: application/json" -X POST -d @hello.payload.json http://localhost:8080/r/myapp/hello
```
Now you can start jobs on your function. Let's quickly queue up a job to try it out. Or surf to it: http://localhost:8080/r/myapp/hello
```sh
cat hello.payload.json | fnctl run goapp /hello
```
Here's a curl example to show how easy it is to do in any language:
```sh
curl -H "Content-Type: application/json" -X POST -d @hello.payload.json http://localhost:8080/r/goapp/hello
```

View File

@@ -1,5 +0,0 @@
app: goapp
route: /hello
image: USERNAME/hello:0.0.1
build:
- docker run --rm -v "$PWD":/go/src/ -w /go/src/ -e "GOPATH=/go/src/vendor:/go" iron/go:dev go build -o hello

View File

@@ -1 +1,3 @@
node_modules/ node_modules/
function.yaml
Dockerfile

View File

@@ -1,6 +0,0 @@
FROM iron/node
WORKDIR /app
ADD . /app
ENTRYPOINT ["node", "hello.js"]

View File

@@ -1,40 +1,18 @@
## Quick Example for a NodeJS Function (4 minutes) ## Quick Example for a NodeJS Function (4 minutes)
This example will show you how to test and deploy Go (Golang) code to IronFunctions. This example will show you how to test and deploy a Node function to IronFunctions.
### 1. Prepare the `functions.yaml` file:
At functions.yaml you will find:
```yml
app: nodeapp
route: /hello
image: USERNAME/hello:0.0.1
build:
- docker run --rm -v "$PWD":/worker -w /worker iron/node:dev npm install
```
The important step here is to ensure you replace `USERNAME` with your Docker Hub account name. Some points of note:
the application name is `nodeapp` and the route for incoming requests is `/hello`. These informations are relevant for
the moment you try to test this function.
### 2. Build:
```sh ```sh
fnctl publish fnctl init <YOUR_DOCKERHUB_USERNAME>/hello
fnctl build
# test it
cat hello.payload.json | fnctl run
# push it to Docker Hub for use with IronFunctions
fnctl push
# Create a route to this function on IronFunctions
fnctl routes create myapp /hello YOUR_DOCKERHUB_USERNAME/hello:0.0.X
# todo: Image name could be optional if we read the function file for creating the route. Then command could be:
fnctl routes create myapp /hello
``` ```
`-v` is optional, but it allows you to see how this function is being built. Now surf to: http://localhost:8080/r/myapp/hello
### 3. Queue jobs for your function
Now you can start jobs on your function. Let's quickly queue up a job to try it out.
```sh
cat hello.payload.json | fnctl run nodeapp /hello
```
Here's a curl example to show how easy it is to do in any language:
```sh
curl -H "Content-Type: application/json" -X POST -d @hello.payload.json http://localhost:8080/r/nodeapp/hello
```

View File

@@ -1,5 +0,0 @@
app: nodeapp
route: /hello
image: USERNAME/hello:0.0.1
build:
- docker run --rm -v "$PWD":/worker -w /worker iron/node:dev npm install

View File

@@ -6,4 +6,4 @@ try {
name = obj.name name = obj.name
} }
} catch(e) {} } catch(e) {}
console.log("Hello", name, "from Node!"); console.log("Hello", name, "from Node!");

1
fnctl/.gitignore vendored
View File

@@ -1,2 +1,3 @@
fnctl fnctl
vendor/ vendor/
/fnctl.exe

View File

@@ -8,7 +8,7 @@ docker: vendor
docker push iron/fnctl docker push iron/fnctl
vendor: vendor:
glide install glide install -v
test: test:
go test -v $(shell glide nv) go test -v $(shell glide nv)

View File

@@ -1,22 +1,17 @@
# IronFunctions CLI # IronFunctions CLI
## Build ## Init
Ensure you have Go configured and installed in your environment. Once it is usage: fnctl init [--runtime node] [--entrypoint "node hello.js"] <name>
done, run:
```sh Init will help you create a function.yaml file for the current directory.
$ make
```
It will build fnctl compatible with your local environment. You can test this If there's a Dockerfile found, this will generate the basic file with just the image name.
CLI, right away with: It will then try to decipher the runtime based on the files in the current directory, if it can't figure it out, it will ask.
It will then take a best guess for what the entrypoint will be based on the language, it it can't guess, it will ask.
```sh
$ ./fnctl
```
## Basic ## Basic
You can operate IronFunctions from the command line. You can operate IronFunctions from the command line.
```sh ```sh
@@ -47,7 +42,7 @@ $ fnctl routes delete otherapp hello # delete route
## Changing target host ## Changing target host
`fnctl` is configured by default to talk to a locally installed IronFunctions. `fnctl` is configured by default to talk http://localhost:8080.
You may reconfigure it to talk to a remote installation by updating a local You may reconfigure it to talk to a remote installation by updating a local
environment variable (`$API_URL`): environment variable (`$API_URL`):
```sh ```sh
@@ -240,3 +235,19 @@ environment variables prefixed with `CONFIG_`.
Repeated calls to `fnctl route create` will trigger an update of the given Repeated calls to `fnctl route create` will trigger an update of the given
route, thus you will be able to change any of these attributes later in time route, thus you will be able to change any of these attributes later in time
if necessary. if necessary.
## Build
Ensure you have Go configured and installed in your environment. Once it is
done, run:
```sh
$ make
```
It will build fnctl compatible with your local environment. You can test this
CLI, right away with:
```sh
$ ./fnctl
```

View File

@@ -36,6 +36,10 @@ func (b *buildcmd) walker(path string, info os.FileInfo, err error, w io.Writer)
// build will take the found valid function and build it // build will take the found valid function and build it
func (b *buildcmd) build(path string) error { func (b *buildcmd) build(path string) error {
fmt.Fprintln(b.verbwriter, "building", path) fmt.Fprintln(b.verbwriter, "building", path)
_, err := b.buildfunc(path) ff, err := b.buildfunc(path)
return err if err != nil {
return err
}
fmt.Printf("Function %v built successfully.\n", ff.FullName())
return nil
} }

View File

@@ -50,16 +50,8 @@ func (b *bumpcmd) bump(path string) error {
} }
if funcfile.Version == "" { if funcfile.Version == "" {
img, ver := imageversion(funcfile.Name) funcfile.Version = initialVersion
if ver == "" {
return nil
}
funcfile.Name = img
funcfile.Version = ver
} else if funcfile.Version != "" && strings.Contains(funcfile.Name, ":") {
return fmt.Errorf("cannot do version bump: this function has tag in its image name and version at same time. name: %s. version: %s", funcfile.Name, funcfile.Version)
} }
s, err := storage.NewVersionStorage("local", funcfile.Version) s, err := storage.NewVersionStorage("local", funcfile.Version)
if err != nil { if err != nil {
return err return err
@@ -73,7 +65,12 @@ func (b *bumpcmd) bump(path string) error {
funcfile.Version = newver.String() funcfile.Version = newver.String()
return storefuncfile(path, funcfile) err = storefuncfile(path, funcfile)
if err != nil {
return err
}
fmt.Println("Bumped to version", funcfile.Version)
return nil
} }
func imageversion(image string) (name, ver string) { func imageversion(image string) (name, ver string) {

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -13,12 +14,13 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/iron-io/functions/fnctl/langs"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var errDockerFileNotFound = errors.New("no Dockerfile found for this function") var errDockerFileNotFound = errors.New("no Dockerfile found for this function")
func isvalid(path string, info os.FileInfo) bool { func isFuncfile(path string, info os.FileInfo) bool {
if info.IsDir() { if info.IsDir() {
return false return false
} }
@@ -38,7 +40,7 @@ func walker(path string, info os.FileInfo, err error, w io.Writer, f func(path s
if err := f(path); err != nil { if err := f(path); err != nil {
fmt.Fprintln(w, err) fmt.Fprintln(w, err)
} else { } else {
fmt.Fprintln(w, "done") // fmt.Fprintln(w, "done")
} }
} }
@@ -87,15 +89,15 @@ func (c *commoncmd) scan(walker func(path string, info os.FileInfo, err error, w
var walked bool var walked bool
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
fmt.Fprint(w, "path", "\t", "result", "\n") // fmt.Fprint(w, "path", "\t", "result", "\n")
err := filepath.Walk(c.wd, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(c.wd, func(path string, info os.FileInfo, err error) error {
// fmt.Println("walking", info.Name())
if !c.recursively && path != c.wd && info.IsDir() { if !c.recursively && path != c.wd && info.IsDir() {
return filepath.SkipDir return filepath.SkipDir
} }
if !isvalid(path, info) { if !isFuncfile(path, info) {
return nil return nil
} }
@@ -114,7 +116,7 @@ func (c *commoncmd) scan(walker func(path string, info os.FileInfo, err error, w
} }
if !walked { if !walked {
fmt.Println("all functions are up-to-date.") fmt.Println("No function file found.")
return return
} }
@@ -184,26 +186,52 @@ func (c commoncmd) localbuild(path string, steps []string) error {
func (c commoncmd) dockerbuild(path string, ff *funcfile) error { func (c commoncmd) dockerbuild(path string, ff *funcfile) error {
dir := filepath.Dir(path) dir := filepath.Dir(path)
var helper langs.LangHelper
dockerfile := filepath.Join(dir, "Dockerfile") dockerfile := filepath.Join(dir, "Dockerfile")
if _, err := os.Stat(dockerfile); os.IsNotExist(err) { if !exists(dockerfile) {
err := writeTmpDockerfile(dir, ff) err := writeTmpDockerfile(dir, ff)
defer os.Remove(filepath.Join(dir, "Dockerfile")) defer os.Remove(filepath.Join(dir, "Dockerfile"))
if err != nil { if err != nil {
return err return err
} }
helper, err = langs.GetLangHelper(*ff.Runtime)
if err != nil {
return err
}
if helper.HasPreBuild() {
err := helper.PreBuild()
if err != nil {
return err
}
}
} }
fmt.Printf("Building image %v\n", ff.FullName())
cmd := exec.Command("docker", "build", "-t", ff.FullName(), ".") cmd := exec.Command("docker", "build", "-t", ff.FullName(), ".")
cmd.Dir = dir cmd.Dir = dir
cmd.Stderr = c.verbwriter cmd.Stderr = os.Stderr
cmd.Stdout = c.verbwriter cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("error running docker build: %v", err) return fmt.Errorf("error running docker build: %v", err)
} }
if helper != nil {
err := helper.AfterBuild()
if err != nil {
return err
}
}
return nil return nil
} }
func exists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
var acceptableFnRuntimes = map[string]string{ var acceptableFnRuntimes = map[string]string{
"elixir": "iron/elixir", "elixir": "iron/elixir",
"erlang": "iron/erlang", "erlang": "iron/erlang",
@@ -221,10 +249,9 @@ var acceptableFnRuntimes = map[string]string{
} }
const tplDockerfile = `FROM {{ .BaseImage }} const tplDockerfile = `FROM {{ .BaseImage }}
WORKDIR /function
ADD ./ / ADD . /function/
ENTRYPOINT [{{ .Entrypoint }}]
ENTRYPOINT ["{{ .Entrypoint }}"]
` `
func writeTmpDockerfile(dir string, ff *funcfile) error { func writeTmpDockerfile(dir string, ff *funcfile) error {
@@ -247,10 +274,23 @@ func writeTmpDockerfile(dir string, ff *funcfile) error {
return err return err
} }
// convert entrypoint string to slice
epvals := strings.Fields(*ff.Entrypoint)
var buffer bytes.Buffer
for i, s := range epvals {
if i > 0 {
buffer.WriteString(", ")
}
buffer.WriteString("\"")
buffer.WriteString(s)
buffer.WriteString("\"")
}
fmt.Println(buffer.String())
t := template.Must(template.New("Dockerfile").Parse(tplDockerfile)) t := template.Must(template.New("Dockerfile").Parse(tplDockerfile))
err = t.Execute(fd, struct { err = t.Execute(fd, struct {
BaseImage, Entrypoint string BaseImage, Entrypoint string
}{rt, *ff.Entrypoint}) }{rt, buffer.String()})
fd.Close() fd.Close()
return err return err
} }

13
fnctl/errors.go Normal file
View File

@@ -0,0 +1,13 @@
package main
type NotFoundError struct {
S string
}
func (e *NotFoundError) Error() string {
return e.S
}
func newNotFoundError(s string) *NotFoundError {
return &NotFoundError{S: s}
}

View File

@@ -14,15 +14,9 @@ import (
var ( var (
validfn = [...]string{ validfn = [...]string{
"functions.yaml",
"functions.yml",
"function.yaml", "function.yaml",
"function.yml", "function.yml",
"fn.yaml",
"fn.yml",
"functions.json",
"function.json", "function.json",
"fn.json",
} }
errUnexpectedFileFormat = errors.New("unexpected file format for function file") errUnexpectedFileFormat = errors.New("unexpected file format for function file")
@@ -63,6 +57,15 @@ func (ff *funcfile) RuntimeTag() (runtime, tag string) {
return rt[:tagpos], rt[tagpos+1:] return rt[:tagpos], rt[tagpos+1:]
} }
func findFuncfile() (*funcfile, error) {
for _, fn := range validfn {
if exists(fn) {
return parsefuncfile(fn)
}
}
return nil, newNotFoundError("could not find function file")
}
func parsefuncfile(path string) (*funcfile, error) { func parsefuncfile(path string) (*funcfile, error) {
ext := filepath.Ext(path) ext := filepath.Ext(path)
switch ext { switch ext {

View File

@@ -1,13 +1,24 @@
package main package main
/*
usage: fnctl init <name>
If there's a Dockerfile found, this will generate the basic file with just the image name. exit
It will then try to decipher the runtime based on the files in the current directory, if it can't figure it out, it will ask.
It will then take a best guess for what the entrypoint will be based on the language, it it can't guess, it will ask.
*/
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/iron-io/functions/fnctl/langs"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -27,6 +38,7 @@ var (
".pl": "perl", ".pl": "perl",
".py": "python", ".py": "python",
".scala": "scala", ".scala": "scala",
".rb": "ruby",
} }
fnRuntimes []string fnRuntimes []string
@@ -39,8 +51,10 @@ func init() {
} }
type initFnCmd struct { type initFnCmd struct {
force bool name string
runtime string force bool
runtime *string
entrypoint *string
} }
func initFn() cli.Command { func initFn() cli.Command {
@@ -49,19 +63,24 @@ func initFn() cli.Command {
return cli.Command{ return cli.Command{
Name: "init", Name: "init",
Usage: "create a local function.yaml file", Usage: "create a local function.yaml file",
Description: "Entrypoint is the binary file which the container engine will invoke when the request comes in - equivalent to Dockerfile ENTRYPOINT.", Description: "Creates a function.yaml file in the current directory. ",
ArgsUsage: "<entrypoint>", ArgsUsage: "<DOCKERHUB_USERNAME:FUNCTION_NAME>",
Action: a.init, Action: a.init,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "f", Name: "force, f",
Usage: "overwrite existing function.yaml", Usage: "overwrite existing function.yaml",
Destination: &a.force, Destination: &a.force,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "runtime", Name: "runtime",
Usage: "choose an existing runtime - " + strings.Join(fnRuntimes, ", "), Usage: "choose an existing runtime - " + strings.Join(fnRuntimes, ", "),
Destination: &a.runtime, Destination: a.runtime,
},
cli.StringFlag{
Name: "entrypoint",
Usage: "entrypoint is the command to run to start this function - equivalent to Dockerfile ENTRYPOINT.",
Destination: a.entrypoint,
}, },
}, },
} }
@@ -69,57 +88,125 @@ func initFn() cli.Command {
func (a *initFnCmd) init(c *cli.Context) error { func (a *initFnCmd) init(c *cli.Context) error {
if !a.force { if !a.force {
for _, fn := range validfn { ff, err := findFuncfile()
if _, err := os.Stat(fn); !os.IsNotExist(err) { if err != nil {
return errors.New("function file already exists") if _, ok := err.(*NotFoundError); ok {
// great, we're about to make one
} else {
return err
} }
} }
if ff != nil {
return errors.New("function file already exists")
}
} }
entrypoint := c.Args().First() err := a.buildFuncFile(c)
if entrypoint == "" {
fmt.Print("Please, specify an entrypoint for your function: ")
fmt.Scanln(&entrypoint)
}
if entrypoint == "" {
return errors.New("entrypoint is missing")
}
pwd, err := os.Getwd()
if err != nil { if err != nil {
return fmt.Errorf("error detecting current working directory: %s\n", err) return err
} }
if a.runtime == "" { /*
rt, err := detectRuntime(pwd) Now we can make some guesses for the entrypoint based on runtime.
if err != nil { If Go, use ./foldername, if ruby, use ruby and a filename. If node, node + filename
return err */
}
var ok bool
a.runtime, ok = fileExtToRuntime[rt]
if !ok {
return fmt.Errorf("could not detect language of this function: %s\n", a.runtime)
}
}
if _, ok := acceptableFnRuntimes[a.runtime]; !ok {
return fmt.Errorf("cannot use runtime %s", a.runtime)
}
ff := &funcfile{ ff := &funcfile{
Runtime: &a.runtime, Name: a.name,
Runtime: a.runtime,
Version: initialVersion, Version: initialVersion,
Entrypoint: &entrypoint, Entrypoint: a.entrypoint,
} }
if err := encodeFuncfileYAML("function.yaml", ff); err != nil { if err := encodeFuncfileYAML("function.yaml", ff); err != nil {
return err return err
} }
fmt.Println("function.yaml written") fmt.Println("function.yaml created.")
return nil return nil
} }
func detectRuntime(path string) (string, error) { func (a *initFnCmd) buildFuncFile(c *cli.Context) error {
pwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("error detecting current working directory: %s\n", err)
}
a.name = c.Args().First()
if a.name == "" {
// todo: also check that it's valid image name format
return errors.New("Please specify a name for your function in the following format <DOCKERHUB_USERNAME>:<FUNCTION_NAME>")
}
if exists("Dockerfile") {
// then don't need anything else
fmt.Println("Dockerfile found, will use that to build.")
return nil
}
var rt string
var filename string
if a.runtime == nil || *a.runtime == "" {
filename, rt, err = detectRuntime(pwd)
if err != nil {
return err
}
a.runtime = &rt
fmt.Printf("assuming %v runtime\n", rt)
}
if _, ok := acceptableFnRuntimes[*a.runtime]; !ok {
return fmt.Errorf("init does not support the %s runtime, you'll have to create your own Dockerfile for this function", *a.runtime)
}
if a.entrypoint == nil || *a.entrypoint == "" {
ep, err := detectEntrypoint(filename, *a.runtime, pwd)
if err != nil {
return fmt.Errorf("could not detect entrypoint for %v, use --entrypoint to add it explicitly. %v", *a.runtime, err)
}
a.entrypoint = &ep
}
return nil
}
// detectRuntime this looks at the files in the directory and if it finds a support file extension, it
// returns the filename and runtime for that extension.
func detectRuntime(path string) (filename string, runtime string, err error) {
err = filepath.Walk(path, func(_ string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
ext := strings.ToLower(filepath.Ext(info.Name()))
if ext == "" {
return nil
}
var ok bool
runtime, ok = fileExtToRuntime[ext]
if ok {
// first match, exiting - http://stackoverflow.com/a/36713726/105562
filename = info.Name()
return io.EOF
}
return nil
})
if err != nil {
if err == io.EOF {
return filename, runtime, nil
}
return "", "", fmt.Errorf("file walk error: %s\n", err)
}
return "", "", fmt.Errorf("no supported files found to guess runtime, please set runtime explicitly with --runtime flag")
}
func detectEntrypoint(filename, runtime, pwd string) (string, error) {
helper, err := langs.GetLangHelper(runtime)
if err != nil {
return "", err
}
return helper.Entrypoint(filename)
}
func scoreExtension(path string) (string, error) {
scores := map[string]uint{ scores := map[string]uint{
"": 0, "": 0,
} }
@@ -145,7 +232,6 @@ func detectRuntime(path string) (string, error) {
biggest = ext biggest = ext
} }
} }
return biggest, nil return biggest, nil
} }

21
fnctl/langs/base.go Normal file
View File

@@ -0,0 +1,21 @@
package langs
import "fmt"
// GetLangHelper returns a LangHelper for the passed in language
func GetLangHelper(lang string) (LangHelper, error) {
switch lang {
case "go":
return &GoLangHelper{}, nil
case "node":
return &NodeLangHelper{}, nil
}
return nil, fmt.Errorf("No language helper found for %v", lang)
}
type LangHelper interface {
Entrypoint(filename string) (string, error)
HasPreBuild() bool
PreBuild() error
AfterBuild() error
}

48
fnctl/langs/go.go Normal file
View File

@@ -0,0 +1,48 @@
package langs
import (
"fmt"
"os"
"os/exec"
"strings"
)
type GoLangHelper struct {
}
func (lh *GoLangHelper) Entrypoint(filename string) (string, error) {
// uses a common binary name: func
// return fmt.Sprintf("./%v", filepath.Base(pwd)), nil
return "./func", nil
}
func (lh *GoLangHelper) HasPreBuild() bool {
return true
}
// PreBuild for Go builds the binary so the final image can be as small as possible
func (lh *GoLangHelper) PreBuild() error {
wd, err := os.Getwd()
if err != nil {
return err
}
// todo: this won't work if the function is more complex since the import paths won't match up, need to fix
pbcmd := fmt.Sprintf("docker run --rm -v %s:/go/src/github.com/x/y -w /go/src/github.com/x/y iron/go:dev go build -o func", wd)
fmt.Println("Running prebuild command:", pbcmd)
parts := strings.Fields(pbcmd)
head := parts[0]
parts = parts[1:len(parts)]
cmd := exec.Command(head, parts...)
// cmd.Dir = dir
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return fmt.Errorf("error running docker build: %v", err)
}
return nil
}
func (lh *GoLangHelper) AfterBuild() error {
return os.Remove("func")
}

23
fnctl/langs/node.go Normal file
View File

@@ -0,0 +1,23 @@
package langs
import "fmt"
type NodeLangHelper struct {
}
func (lh *NodeLangHelper) Entrypoint(filename string) (string, error) {
return fmt.Sprintf("node %v", filename), nil
}
func (lh *NodeLangHelper) HasPreBuild() bool {
return false
}
// PreBuild for Go builds the binary so the final image can be as small as possible
func (lh *NodeLangHelper) PreBuild() error {
return nil
}
func (lh *NodeLangHelper) AfterBuild() error {
return nil
}

View File

@@ -83,9 +83,10 @@ func (p *publishcmd) publish(path string) error {
} }
func (p publishcmd) dockerpush(ff *funcfile) error { func (p publishcmd) dockerpush(ff *funcfile) error {
fmt.Printf("Pushing function %v to Docker Hub.\n", ff.FullName())
cmd := exec.Command("docker", "push", ff.FullName()) cmd := exec.Command("docker", "push", ff.FullName())
cmd.Stderr = p.verbwriter cmd.Stderr = os.Stderr
cmd.Stdout = p.verbwriter cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("error running docker push: %v", err) return fmt.Errorf("error running docker push: %v", err)
} }

View File

@@ -20,7 +20,7 @@ func push() cli.Command {
flags = append(flags, cmd.commoncmd.flags()...) flags = append(flags, cmd.commoncmd.flags()...)
return cli.Command{ return cli.Command{
Name: "push", Name: "push",
Usage: "scan local directory for functions and push them.", Usage: "push function to Docker Hub",
Flags: flags, Flags: flags,
Action: cmd.scan, Action: cmd.scan,
} }
@@ -55,10 +55,5 @@ func (p *pushcmd) push(path string) error {
if err := p.dockerpush(funcfile); err != nil { if err := p.dockerpush(funcfile); err != nil {
return err return err
} }
if err := p.route(path, funcfile); err != nil {
return err
}
return nil return nil
} }

View File

@@ -36,7 +36,16 @@ func runflags() []cli.Flag {
func (r *runCmd) run(c *cli.Context) error { func (r *runCmd) run(c *cli.Context) error {
image := c.Args().First() image := c.Args().First()
if image == "" { if image == "" {
return errors.New("error: image name is missing") // check for a funcfile
ff, err := findFuncfile()
if err != nil {
if _, ok := err.(*NotFoundError); ok {
return errors.New("error: image name is missing or no function file found")
} else {
return err
}
}
image = ff.FullName()
} }
sh := []string{"docker", "run", "--rm", "-i"} sh := []string{"docker", "run", "--rm", "-i"}
@@ -60,7 +69,24 @@ func (r *runCmd) run(c *cli.Context) error {
sh = append(sh, image) sh = append(sh, image)
cmd := exec.Command(sh[0], sh[1:]...) cmd := exec.Command(sh[0], sh[1:]...)
cmd.Stdin = os.Stdin // Check if stdin is being piped, and if not, create our own pipe with nothing in it
// http://stackoverflow.com/questions/22744443/check-if-there-is-something-to-read-on-stdin-in-golang
stat, err := os.Stdin.Stat()
if err != nil {
// On Windows, this gets an error if nothing is piped in.
// If something is piped in, it works fine.
// Turns out, this works just fine in our case as the piped stuff works properly and the non-piped doesn't hang either.
// See: https://github.com/golang/go/issues/14853#issuecomment-260170423
// log.Println("Warning: couldn't stat stdin, you are probably on Windows. Be sure to pipe something into this command, eg: 'echo \"hello\" | fnctl run'")
} else {
if (stat.Mode() & os.ModeCharDevice) == 0 {
// log.Println("data is being piped to stdin")
cmd.Stdin = os.Stdin
} else {
// log.Println("stdin is from a terminal")
cmd.Stdin = strings.NewReader("")
}
}
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = env cmd.Env = env