Merge branch 'master' into slack

This commit is contained in:
Travis Reeder
2017-08-23 12:14:38 -07:00
committed by GitHub
19 changed files with 269 additions and 246 deletions

View File

@@ -49,17 +49,19 @@ configuration options [here](docs/operating/options.md). If you are on Windows,
Functions are small but powerful blocks of code that generally do one simple thing. Forget about monoliths when using functions, just focus on the task that you want the function to perform.
The following is a simple Go program that outputs a string to STDOUT. Copy and paste the code below into a file called `func.go`. Currently the function must be named func.your_language_extention (ie func.go, func.js, etc.)
First, create an empty directory called `hello` and cd into it.
The following is a simple Go program that outputs a string to STDOUT. Copy and paste the code below into a file called `func.go`.
```go
package main
import (
"fmt"
"fmt"
)
func main() {
fmt.Println("Hello from Fn!")
fmt.Println("Hello from Fn!")
}
```
@@ -68,12 +70,15 @@ Now run the following CLI commands:
```sh
# Initialize your function
# This detects your runtime from the code above and creates a func.yaml
fn init <DOCKERHUB_USERNAME>/hello
fn init
# Test your function
# This will run inside a container exactly how it will on the server
fn run
# Set your Docker Hub username
export FN_REGISTRY=<DOCKERHUB_USERNAME>
# Deploy your functions to the Fn server (default localhost:8080)
# This will create a route to your function as well
fn deploy myapp
@@ -83,19 +88,21 @@ Now you can call your function:
```sh
curl http://localhost:8080/r/myapp/hello
# or:
fn call myapp /hello
```
Or in a browser: [http://localhost:8080/r/myapp/hello](http://localhost:8080/r/myapp/hello)
That's it! You just deployed your first function and called it. Now to update your function
That's it! You just deployed your first function and called it. To update your function
you can update your code and run `fn deploy myapp` again.
## To Learn More
- Visit our Functions [Tutorial Series](examples/tutorial/)
- See our [full documentation](docs/README.md)
- View all of our [examples](/examples)
- You can also write your functions in AWS [Lambda format](docs/lambda/README.md)
* Visit our Functions [Tutorial Series](examples/tutorial/)
* See our [full documentation](docs/README.md)
* View all of our [examples](/examples)
* You can also write your functions in AWS [Lambda format](docs/lambda/README.md)
## Get Involved
@@ -103,7 +110,6 @@ you can update your code and run `fn deploy myapp` again.
- Learn how to [contribute](CONTRIBUTING.md)
- See [milestones](https://github.com/fnproject/fn/milestones) for detailed issues
## User Interface
This is the graphical user interface for Fn. It is currently not buildable.
@@ -114,9 +120,8 @@ docker run --rm -it --link functions:api -p 4000:4000 -e "API_URL=http://api:808
For more information, see: [https://github.com/treeder/functions-ui](https://github.com/treeder/functions-ui)
## Next up
# Next up
### Check out the [Tutorial Series](examples/tutorial/).
### Check out the [Tutorial Series](examples/tutorial/)
It will demonstrate some of Fn capabilities through a series of exmaples. We'll try to show examples in most major languages. This is a great place to start!

View File

@@ -4,6 +4,9 @@ all: vendor build
build:
go build -o fn
install:
go build -o ${GOPATH}/bin/fn
docker: vendor
GOOS=linux go build -o fn
docker build -t treeder/fn .
@@ -23,3 +26,5 @@ release:
GOOS=darwin go build -o fn_mac
GOOS=windows go build -o fn.exe
docker run --rm -v ${PWD}:/go/src/github.com/fnproject/fn/cli -w /go/src/github.com/fnproject/fn/cli funcy/go:dev go build -o fn_alpine
.PHONY: install

View File

@@ -12,13 +12,13 @@ if you are using Node, put the code that you want to execute in the file `func.j
Run:
```sh
fn init <DOCKER_HUB_USERNAME>/<FUNCTION_NAME>
fn init [<FUNCTION_NAME>]
```
If you want to override the convention with configuration, you can do that as well using:
```sh
fn init [--runtime node] [--entrypoint "node hello.js"] <DOCKER_HUB_USERNAME>/<FUNCTION_NAME>
fn init [--runtime node] [--entrypoint "node hello.js"] [<FUNCTION_NAME>]
```
Or, if you want full control, just make a Dockerfile. If `init` finds a Dockerfile, it will use that instead of runtime and entrypoint.

View File

@@ -102,17 +102,17 @@ func (a *appsCmd) list(c *cli.Context) error {
})
if err != nil {
// fmt.Println("err type:", reflect.TypeOf(err))
switch err.(type) {
switch e := err.(type) {
case *apiapps.GetAppsAppNotFound:
return fmt.Errorf("error: %v", err.(*apiapps.GetAppsAppNotFound).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
case *apiapps.GetAppsAppDefault:
return fmt.Errorf("unexpected error: %v", err.(*apiapps.GetAppsAppDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %v", e.Payload.Error.Message)
case *apiapps.GetAppsDefault:
// this is the one getting called, not sure what the one above is?
return fmt.Errorf("unexpected error: %v", err.(*apiapps.GetAppsDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
if len(resp.Payload.Apps) == 0 {
@@ -139,15 +139,16 @@ func (a *appsCmd) create(c *cli.Context) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiapps.PostAppsBadRequest:
return fmt.Errorf("error: %v", err.(*apiapps.PostAppsBadRequest).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
case *apiapps.PostAppsConflict:
return fmt.Errorf("error: %v", err.(*apiapps.PostAppsConflict).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
case *apiapps.PostAppsDefault:
return fmt.Errorf("unexpected error: %v", err.(*apiapps.PostAppsDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
fmt.Println("Successfully created app: ", resp.Payload.App.Name)
@@ -215,15 +216,16 @@ func (a *appsCmd) patchApp(appName string, app *models.App) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiapps.PatchAppsAppBadRequest:
return errors.New(err.(*apiapps.PatchAppsAppBadRequest).Payload.Error.Message)
return errors.New(e.Payload.Error.Message)
case *apiapps.PatchAppsAppNotFound:
return errors.New(err.(*apiapps.PatchAppsAppNotFound).Payload.Error.Message)
return errors.New(e.Payload.Error.Message)
case *apiapps.PatchAppsAppDefault:
return errors.New(err.(*apiapps.PatchAppsAppDefault).Payload.Error.Message)
return errors.New(e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
return nil
@@ -243,13 +245,14 @@ func (a *appsCmd) inspect(c *cli.Context) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiapps.GetAppsAppNotFound:
return fmt.Errorf("error: %v", err.(*apiapps.GetAppsAppNotFound).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
case *apiapps.GetAppsAppDefault:
return fmt.Errorf("unexpected error: %v", err.(*apiapps.GetAppsAppDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
enc := json.NewEncoder(os.Stdout)
@@ -294,11 +297,11 @@ func (a *appsCmd) delete(c *cli.Context) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiapps.DeleteAppsAppNotFound:
return errors.New(err.(*apiapps.DeleteAppsAppNotFound).Payload.Error.Message)
return errors.New(e.Payload.Error.Message)
case *apiapps.DeleteAppsAppDefault:
return errors.New(err.(*apiapps.DeleteAppsAppDefault).Payload.Error.Message)
return errors.New(e.Payload.Error.Message)
}
return fmt.Errorf("unexpected error: %v", err)
}

View File

@@ -40,8 +40,6 @@ func (b *buildcmd) flags() []cli.Flag {
// build will take the found valid function and build it
func (b *buildcmd) build(c *cli.Context) error {
verbwriter := verbwriter(b.verbose)
path, err := os.Getwd()
if err != nil {
return err
@@ -51,11 +49,11 @@ func (b *buildcmd) build(c *cli.Context) error {
return err
}
ff, err := buildfunc(verbwriter, fn, b.noCache)
ff, err := buildfunc(fn, b.noCache)
if err != nil {
return err
}
fmt.Printf("Function %v built successfully.\n", ff.FullName())
fmt.Printf("Function %v built successfully.\n", ff.ImageName())
return nil
}

View File

@@ -41,7 +41,6 @@ func (b *bumpcmd) flags() []cli.Flag {
// bump will take the found valid function and bump its version
func (b *bumpcmd) bump(c *cli.Context) error {
verbwriter := verbwriter(b.verbose)
path, err := os.Getwd()
if err != nil {
@@ -52,7 +51,7 @@ func (b *bumpcmd) bump(c *cli.Context) error {
return err
}
fmt.Fprintln(verbwriter, "bumping version for", fn)
fmt.Println("bumping version for", fn)
funcfile, err := parsefuncfile(fn)
if err != nil {

View File

@@ -64,12 +64,12 @@ func (call *callsCmd) get(ctx *cli.Context) error {
}
resp, err := call.client.Call.GetAppsAppCallsCall(&params)
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apicall.GetAppsAppCallsCallNotFound:
return fmt.Errorf("error: %v", err.(*apicall.GetAppsAppCallsCallNotFound).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
printCalls([]*models.Call{resp.Payload.Call})
return nil
@@ -87,12 +87,12 @@ func (call *callsCmd) list(ctx *cli.Context) error {
}
resp, err := call.client.Call.GetAppsAppCalls(&params)
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apicall.GetCallsCallNotFound:
return fmt.Errorf("error: %v", err.(*apicall.GetCallsCallNotFound).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
printCalls(resp.Payload.Calls)
return nil

View File

@@ -11,6 +11,10 @@ import (
"github.com/go-openapi/strfmt"
)
const (
envFnToken = "FN_TOKEN"
)
func Host() string {
apiURL := os.Getenv("API_URL")
if apiURL == "" {
@@ -26,8 +30,8 @@ func Host() string {
func APIClient() *fnclient.Functions {
transport := httptransport.New(Host(), "/v1", []string{"http"})
if os.Getenv("FN_TOKEN") != "" {
transport.DefaultAuthentication = httptransport.BearerToken(os.Getenv("FN_TOKEN"))
if os.Getenv(envFnToken) != "" {
transport.DefaultAuthentication = httptransport.BearerToken(os.Getenv(envFnToken))
}
// create the API client, with the transport

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"os/signal"
@@ -22,18 +23,23 @@ import (
const (
functionsDockerImage = "funcy/functions"
minRequiredDockerVersion = "17.5.0"
envFnRegistry = "FN_REGISTRY"
)
func verbwriter(verbose bool) io.Writer {
// this is too limiting, removes all logs which isn't what we want
// verbwriter := ioutil.Discard
// if verbose {
verbwriter := os.Stderr
// }
return verbwriter
type HasRegistry interface {
Registry() string
}
func buildfunc(verbwriter io.Writer, fn string, noCache bool) (*funcfile, error) {
func setRegistryEnv(hr HasRegistry) {
if hr.Registry() != "" {
err := os.Setenv(envFnRegistry, hr.Registry())
if err != nil {
log.Fatalf("Couldn't set %s env var: %v\n", envFnRegistry, err)
}
}
}
func buildfunc(fn string, noCache bool) (*funcfile, error) {
funcfile, err := parsefuncfile(fn)
if err != nil {
return nil, err
@@ -53,23 +59,21 @@ func buildfunc(verbwriter io.Writer, fn string, noCache bool) (*funcfile, error)
}
}
if err := localbuild(verbwriter, fn, funcfile.Build); err != nil {
if err := localbuild(fn, funcfile.Build); err != nil {
return nil, err
}
if err := dockerbuild(verbwriter, fn, funcfile, noCache); err != nil {
if err := dockerbuild(fn, funcfile, noCache); err != nil {
return nil, err
}
return funcfile, nil
}
func localbuild(verbwriter io.Writer, path string, steps []string) error {
func localbuild(path string, steps []string) error {
for _, cmd := range steps {
exe := exec.Command("/bin/sh", "-c", cmd)
exe.Dir = filepath.Dir(path)
exe.Stderr = verbwriter
exe.Stdout = verbwriter
if err := exe.Run(); err != nil {
return fmt.Errorf("error running command %v (%v)", cmd, err)
}
@@ -78,7 +82,7 @@ func localbuild(verbwriter io.Writer, path string, steps []string) error {
return nil
}
func dockerbuild(verbwriter io.Writer, path string, ff *funcfile, noCache bool) error {
func dockerbuild(path string, ff *funcfile, noCache bool) error {
err := dockerVersionCheck()
if err != nil {
return err
@@ -89,9 +93,9 @@ func dockerbuild(verbwriter io.Writer, path string, ff *funcfile, noCache bool)
var helper langs.LangHelper
dockerfile := filepath.Join(dir, "Dockerfile")
if !exists(dockerfile) {
helper = langs.GetLangHelper(*ff.Runtime)
helper = langs.GetLangHelper(ff.Runtime)
if helper == nil {
return fmt.Errorf("Cannot build, no language helper found for %v", *ff.Runtime)
return fmt.Errorf("Cannot build, no language helper found for %v", ff.Runtime)
}
dockerfile, err = writeTmpDockerfile(helper, dir, ff)
if err != nil {
@@ -106,7 +110,7 @@ func dockerbuild(verbwriter io.Writer, path string, ff *funcfile, noCache bool)
}
}
fmt.Printf("Building image %v\n", ff.FullName())
fmt.Printf("Building image %v\n", ff.ImageName())
cancel := make(chan os.Signal, 3)
signal.Notify(cancel, os.Interrupt) // and others perhaps
@@ -117,7 +121,7 @@ func dockerbuild(verbwriter io.Writer, path string, ff *funcfile, noCache bool)
go func(done chan<- error) {
args := []string{
"build",
"-t", ff.FullName(),
"-t", ff.ImageName(),
"-f", dockerfile,
}
if noCache {
@@ -261,8 +265,12 @@ func extractEnvConfig(configs []string) map[string]string {
}
func dockerpush(ff *funcfile) error {
fmt.Println("Pushing to docker registry...")
cmd := exec.Command("docker", "push", ff.FullName())
err := validImageName(ff.ImageName())
if err != nil {
return err
}
fmt.Printf("Pushing %v to docker registry...", ff.ImageName())
cmd := exec.Command("docker", "push", ff.ImageName())
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
@@ -271,6 +279,19 @@ func dockerpush(ff *funcfile) error {
return nil
}
func validImageName(n string) error {
// must have at least owner name and a tag
split := strings.Split(n, ":")
if len(split) < 2 {
return errors.New("image name must have a tag")
}
split2 := strings.Split(split[0], "/")
if len(split2) < 2 {
return errors.New("image name must have an owner and name, eg: username/myfunc. Be sure to set FN_REGISTRY env var or pass in --registry.")
}
return nil
}
func appNamePath(img string) (string, string) {
sep := strings.Index(img, "/")
if sep < 0 {

View File

@@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
"io"
"log"
"os"
"path"
@@ -40,8 +39,11 @@ type deploycmd struct {
incremental bool
skippush bool
noCache bool
registry string
}
verbwriter io.Writer
func (cmd *deploycmd) Registry() string {
return cmd.registry
}
func (p *deploycmd) flags() []cli.Flag {
@@ -73,18 +75,23 @@ func (p *deploycmd) flags() []cli.Flag {
Usage: "does not push Docker built images onto Docker Hub - useful for local development.",
Destination: &p.skippush,
},
cli.StringFlag{
Name: "registry",
Usage: "Sets the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries. eg: `--registry username` will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner.",
Destination: &p.registry,
},
}
}
func (p *deploycmd) scan(c *cli.Context) error {
p.appName = c.Args().First()
p.verbwriter = verbwriter(p.verbose)
var walked bool
wd, err := os.Getwd()
if err != nil {
log.Fatalln("Couldn't get working directory:", err)
}
setRegistryEnv(p)
err = filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
if path != wd && info.IsDir() {
@@ -101,7 +108,7 @@ func (p *deploycmd) scan(c *cli.Context) error {
e := p.deploy(c, path)
if err != nil {
fmt.Fprintln(p.verbwriter, path, e)
fmt.Println(path, e)
}
now := time.Now()
@@ -110,7 +117,7 @@ func (p *deploycmd) scan(c *cli.Context) error {
return e
})
if err != nil {
fmt.Fprintf(p.verbwriter, "error: %s\n", err)
fmt.Printf("error: %s\n", err)
}
if !walked {
@@ -132,7 +139,7 @@ func (p *deploycmd) deploy(c *cli.Context, funcFilePath string) error {
return err
}
funcfile, err := buildfunc(p.verbwriter, funcFileName, p.noCache)
funcfile, err := buildfunc(funcFileName, p.noCache)
if err != nil {
return err
}
@@ -152,14 +159,14 @@ func (p *deploycmd) deploy(c *cli.Context, funcFilePath string) error {
}
func (p *deploycmd) route(c *cli.Context, ff *funcfile) error {
fmt.Printf("Updating route %s using image %s...\n", ff.Path, ff.FullName())
fmt.Printf("Updating route %s using image %s...\n", ff.Path, ff.ImageName())
if err := resetBasePath(p.Configuration); err != nil {
return fmt.Errorf("error setting endpoint: %v", err)
}
routesCmd := routesCmd{client: client.APIClient()}
rt := &models.Route{}
if err := routeWithFuncFile(c, ff, rt); err != nil {
if err := routeWithFuncFile(ff, rt); err != nil {
return fmt.Errorf("error getting route with funcfile: %s", err)
}
return routesCmd.putRoute(c, p.appName, ff.Path, rt)

View File

@@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
fnmodels "github.com/funcy/functions_go/models"
yaml "gopkg.in/yaml.v2"
)
@@ -39,19 +38,37 @@ type fftest struct {
}
type funcfile struct {
fnmodels.Route
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Runtime *string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Entrypoint string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Build []string `yaml:"build,omitempty" json:"build,omitempty"`
Tests []fftest `yaml:"tests,omitempty" json:"tests,omitempty"`
// route specific
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Memory uint64 `yaml:"memory,omitempty" json:"memory,omitempty"`
Format string `yaml:"format,omitempty" json:"format,omitempty"`
Timeout *int32 `yaml:"timeout,omitempty" json:"timeout,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Config map[string]string `yaml:"config,omitempty" json:"config,omitempty"`
Headers map[string][]string `yaml:"headers,omitempty" json:"headers,omitempty"`
IDLETimeout *int32 `yaml:"idle_timeout,omitempty" json:"idle_timeout,omitempty"`
}
func (ff *funcfile) FullName() string {
func (ff *funcfile) ImageName() string {
fname := ff.Name
if !strings.Contains(fname, "/") {
// then we'll prefix FN_REGISTRY
reg := os.Getenv(envFnRegistry)
if reg != "" {
if reg[len(reg)-1] != '/' {
reg += "/"
}
fname = fmt.Sprintf("%s%s", reg, fname)
}
}
if ff.Version != "" {
fname = fmt.Sprintf("%s:%s", fname, ff.Version)
}
@@ -59,11 +76,11 @@ func (ff *funcfile) FullName() string {
}
func (ff *funcfile) RuntimeTag() (runtime, tag string) {
if ff.Runtime == nil {
if ff.Runtime == "" {
return "", ""
}
rt := *ff.Runtime
rt := ff.Runtime
tagpos := strings.Index(rt, ":")
if tagpos == -1 {
return rt, ""
@@ -72,65 +89,6 @@ func (ff *funcfile) RuntimeTag() (runtime, tag string) {
return rt[:tagpos], rt[tagpos+1:]
}
type flatfuncfile struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Runtime *string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Entrypoint string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Build []string `yaml:"build,omitempty" json:"build,omitempty"`
Tests []fftest `yaml:"tests,omitempty" json:"tests,omitempty"`
// route specific
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Memory uint64 `yaml:"memory,omitempty" json:"memory,omitempty"`
Format string `yaml:"format,omitempty" json:"format,omitempty"`
Timeout *int32 `yaml:"timeout,omitempty" json:"timeout,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Config map[string]string `yaml:"config,omitempty" json:"config,omitempty"`
Headers map[string][]string `yaml:"headers,omitempty" json:"headers,omitempty"`
}
func (ff *funcfile) MakeFlat() flatfuncfile {
return flatfuncfile{
Name: ff.Name,
Version: ff.Version,
Runtime: ff.Runtime,
Entrypoint: ff.Entrypoint,
Cmd: ff.Cmd,
Build: ff.Build,
Tests: ff.Tests,
// route-specific
Type: ff.Type,
Memory: ff.Memory,
Format: ff.Format,
Timeout: ff.Timeout,
Path: ff.Path,
Config: ff.Config,
Headers: ff.Headers,
}
}
func (fff *flatfuncfile) MakeFuncFile() *funcfile {
ff := &funcfile{
Name: fff.Name,
Version: fff.Version,
Runtime: fff.Runtime,
Entrypoint: fff.Entrypoint,
Cmd: fff.Cmd,
Build: fff.Build,
Tests: fff.Tests,
}
ff.Type = fff.Type
ff.Memory = fff.Memory
ff.Format = fff.Format
ff.Timeout = fff.Timeout
ff.Path = fff.Path
ff.Config = fff.Config
ff.Headers = fff.Headers
return ff
}
func findFuncfile(path string) (string, error) {
for _, fn := range validfn {
fullfn := filepath.Join(path, fn)
@@ -176,9 +134,10 @@ func decodeFuncfileJSON(path string) (*funcfile, error) {
if err != nil {
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
}
fff := new(flatfuncfile)
err = json.NewDecoder(f).Decode(fff)
ff := fff.MakeFuncFile()
ff := &funcfile{}
// ff.Route = &fnmodels.Route{}
err = json.NewDecoder(f).Decode(ff)
// ff := fff.MakeFuncFile()
return ff, err
}
@@ -187,9 +146,9 @@ func decodeFuncfileYAML(path string) (*funcfile, error) {
if err != nil {
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
}
fff := new(flatfuncfile)
err = yaml.Unmarshal(b, fff)
ff := fff.MakeFuncFile()
ff := &funcfile{}
err = yaml.Unmarshal(b, ff)
// ff := fff.MakeFuncFile()
return ff, err
}
@@ -198,11 +157,11 @@ func encodeFuncfileJSON(path string, ff *funcfile) error {
if err != nil {
return fmt.Errorf("could not open %s for encoding. Error: %v", path, err)
}
return json.NewEncoder(f).Encode(ff.MakeFlat())
return json.NewEncoder(f).Encode(ff)
}
func encodeFuncfileYAML(path string, ff *funcfile) error {
b, err := yaml.Marshal(ff.MakeFlat())
b, err := yaml.Marshal(ff)
if err != nil {
return fmt.Errorf("could not encode function file. Error: %v", err)
}

View File

@@ -45,12 +45,8 @@ func init() {
}
type initFnCmd struct {
name string
force bool
runtime string
entrypoint string
cmd string
version string
force bool
funcfile
}
func initFlags(a *initFnCmd) []cli.Flag {
@@ -63,17 +59,22 @@ func initFlags(a *initFnCmd) []cli.Flag {
cli.StringFlag{
Name: "runtime",
Usage: "choose an existing runtime - " + strings.Join(fnInitRuntimes, ", "),
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,
Destination: &a.Entrypoint,
},
cli.StringFlag{
Name: "cmd",
Usage: "command to run to start this function - equivalent to Dockerfile CMD.",
Destination: &a.Entrypoint,
},
cli.StringFlag{
Name: "version",
Usage: "function version",
Destination: &a.version,
Destination: &a.Version,
Value: initialVersion,
},
}
@@ -82,15 +83,16 @@ func initFlags(a *initFnCmd) []cli.Flag {
}
func initFn() cli.Command {
a := initFnCmd{}
a := &initFnCmd{}
// funcfile := &funcfile{}
return cli.Command{
Name: "init",
Usage: "create a local func.yaml file",
Description: "Creates a func.yaml file in the current directory. ",
ArgsUsage: "<DOCKERHUB_USERNAME/FUNCTION_NAME>",
Description: "Creates a func.yaml file in the current directory.",
ArgsUsage: "[FUNCTION_NAME]",
Action: a.init,
Flags: initFlags(&a),
Flags: initFlags(a),
}
}
@@ -109,13 +111,13 @@ func (a *initFnCmd) init(c *cli.Context) error {
}
}
runtimeSpecified := a.runtime != ""
err := a.buildFuncFile(c)
if err != nil {
return err
}
runtimeSpecified := a.Runtime != ""
if runtimeSpecified {
err := a.generateBoilerplate()
if err != nil {
@@ -123,21 +125,12 @@ func (a *initFnCmd) init(c *cli.Context) error {
}
}
ff := &funcfile{
*rt,
a.name,
a.version,
&a.runtime,
a.entrypoint,
a.cmd,
[]string{},
[]fftest{},
}
ff := a.funcfile
_, path := appNamePath(ff.FullName())
_, path := appNamePath(ff.ImageName())
ff.Path = path
if err := encodeFuncfileYAML("func.yaml", ff); err != nil {
if err := encodeFuncfileYAML("func.yaml", &ff); err != nil {
return err
}
@@ -146,7 +139,7 @@ func (a *initFnCmd) init(c *cli.Context) error {
}
func (a *initFnCmd) generateBoilerplate() error {
helper := langs.GetLangHelper(a.runtime)
helper := langs.GetLangHelper(a.Runtime)
if helper != nil && helper.HasBoilerplate() {
if err := helper.GenerateBoilerplate(); err != nil {
if err == langs.ErrBoilerplateExists {
@@ -162,47 +155,52 @@ func (a *initFnCmd) generateBoilerplate() 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", err)
return fmt.Errorf("error detecting current working directory: %v", err)
}
a.name = c.Args().First()
if a.name == "" || strings.Contains(a.name, ":") {
return errors.New("please specify a name for your function in the following format <DOCKERHUB_USERNAME>/<FUNCTION_NAME>.\nTry: fn init <DOCKERHUB_USERNAME>/<FUNCTION_NAME>")
a.Name = c.Args().First()
// if a.name == "" {
// // return errors.New("please specify a name for your function.\nTry: fn init <FUNCTION_NAME>")
// } else
if a.Name == "" {
// then use current directory for name
a.Name = filepath.Base(pwd)
} else if strings.Contains(a.Name, ":") {
return errors.New("function name cannot contain a colon")
}
if exists("Dockerfile") {
fmt.Println("Dockerfile found. Let's use that to build...")
return nil
}
var rt string
if a.runtime == "" {
if a.Runtime == "" {
rt, err = detectRuntime(pwd)
if err != nil {
return err
}
a.runtime = rt
a.Runtime = rt
fmt.Printf("Found %v, assuming %v runtime.\n", rt, rt)
} else {
fmt.Println("Runtime:", a.runtime)
fmt.Println("Runtime:", a.Runtime)
}
helper := langs.GetLangHelper(a.runtime)
helper := langs.GetLangHelper(a.Runtime)
if helper == nil {
fmt.Printf("init does not support the %s runtime, you'll have to create your own Dockerfile for this function", a.runtime)
fmt.Printf("init does not support the %s runtime, you'll have to create your own Dockerfile for this function", a.Runtime)
}
if a.entrypoint == "" {
if a.Entrypoint == "" {
if helper != nil {
a.entrypoint = helper.Entrypoint()
a.Entrypoint = helper.Entrypoint()
}
}
if a.cmd == "" {
if a.Cmd == "" {
if helper != nil {
a.cmd = helper.Cmd()
a.Cmd = helper.Cmd()
}
}
if a.entrypoint == "" && a.cmd == "" {
return fmt.Errorf("could not detect entrypoint or cmd for %v, use --entrypoint and/or --cmd to set them explicitly", a.runtime)
if a.Entrypoint == "" && a.Cmd == "" {
return fmt.Errorf("could not detect entrypoint or cmd for %v, use --entrypoint and/or --cmd to set them explicitly", a.Runtime)
}
return nil
@@ -221,5 +219,5 @@ func detectRuntime(path string) (runtime string, err error) {
}
}
}
return "", fmt.Errorf("no supported files found to guess runtime, please set runtime explicitly with --runtime flag.")
return "", fmt.Errorf("no supported files found to guess runtime, please set runtime explicitly with --runtime flag")
}

View File

@@ -181,7 +181,7 @@ func createFunctionYaml(opts createImageOptions, functionName string) error {
funcDesc := &funcfile{
Name: opts.Name,
Version: "0.0.1",
Runtime: &opts.Base,
Runtime: opts.Base,
Cmd: opts.Handler,
}
funcDesc.Config = opts.Config

View File

@@ -41,12 +41,12 @@ func (log *logsCmd) get(ctx *cli.Context) error {
}
resp, err := log.client.Operations.GetAppsAppCallsCallLog(&params)
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apicall.GetAppsAppCallsCallLogNotFound:
return fmt.Errorf("error: %v", err.(*apicall.GetAppsAppCallsCallLogNotFound).Payload.Error.Message)
return fmt.Errorf("error: %v", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %v", err)
}
fmt.Print(resp.Payload.Log.Log)
return nil

View File

@@ -132,7 +132,7 @@ func main() {
err := app.Run(os.Args)
if err != nil {
// TODO: this doesn't seem to get called even when an error returns from a command, but maybe urfave is doing a non zero exit anyways? nope: https://github.com/urfave/cli/issues/610
fmt.Printf("Error occurred: %v, exiting...\n", err)
fmt.Fprintf(os.Stderr, "Error occurred: %v, exiting...\n", err)
os.Exit(1)
}
}

View File

@@ -20,7 +20,12 @@ func push() cli.Command {
}
type pushcmd struct {
verbose bool
verbose bool
registry string
}
func (cmd *pushcmd) Registry() string {
return cmd.registry
}
func (p *pushcmd) flags() []cli.Flag {
@@ -30,6 +35,11 @@ func (p *pushcmd) flags() []cli.Flag {
Usage: "verbose mode",
Destination: &p.verbose,
},
cli.StringFlag{
Name: "registry",
Usage: "Sets the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries. eg: `--registry username` will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner.",
Destination: &p.registry,
},
}
}
@@ -38,7 +48,7 @@ func (p *pushcmd) flags() []cli.Flag {
// push the container, and finally it will update function's route. Optionally,
// the route can be overriden inside the functions file.
func (p *pushcmd) push(c *cli.Context) error {
verbwriter := verbwriter(p.verbose)
setRegistryEnv(p)
ff, err := loadFuncfile()
if err != nil {
@@ -48,12 +58,12 @@ func (p *pushcmd) push(c *cli.Context) error {
return err
}
fmt.Fprintln(verbwriter, "pushing", ff.FullName())
fmt.Println("pushing", ff.ImageName())
if err := dockerpush(ff); err != nil {
return err
}
fmt.Printf("Function %v pushed successfully to Docker Hub.\n", ff.FullName())
fmt.Printf("Function %v pushed successfully to Docker Hub.\n", ff.ImageName())
return nil
}

View File

@@ -181,13 +181,14 @@ func (a *routesCmd) list(c *cli.Context) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.GetAppsAppRoutesNotFound:
return fmt.Errorf("error: %s", err.(*apiroutes.GetAppsAppRoutesNotFound).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.GetAppsAppRoutesDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.GetAppsAppRoutesDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
@@ -260,7 +261,7 @@ func routeWithFlags(c *cli.Context, rt *fnmodels.Route) {
}
}
func routeWithFuncFile(c *cli.Context, ff *funcfile, rt *fnmodels.Route) error {
func routeWithFuncFile(ff *funcfile, rt *fnmodels.Route) error {
var err error
if ff == nil {
ff, err = loadFuncfile()
@@ -268,8 +269,8 @@ func routeWithFuncFile(c *cli.Context, ff *funcfile, rt *fnmodels.Route) error {
return err
}
}
if ff.FullName() != "" { // args take precedence
rt.Image = ff.FullName()
if ff.ImageName() != "" { // args take precedence
rt.Image = ff.ImageName()
}
if ff.Format != "" {
rt.Format = ff.Format
@@ -286,7 +287,15 @@ func routeWithFuncFile(c *cli.Context, ff *funcfile, rt *fnmodels.Route) error {
if ff.Memory != 0 {
rt.Memory = ff.Memory
}
// TODO idle_timeout? headers? config? why is a func file not a yaml unmarshal of a route?
if rt.IDLETimeout != nil {
rt.IDLETimeout = ff.IDLETimeout
}
if len(rt.Headers) != 0 {
rt.Headers = ff.Headers
}
if len(rt.Config) != 0 {
rt.Config = ff.Config
}
return nil
}
@@ -299,7 +308,7 @@ func (a *routesCmd) create(c *cli.Context) error {
rt.Path = route
rt.Image = c.Args().Get(2)
if err := routeWithFuncFile(c, nil, rt); err != nil {
if err := routeWithFuncFile(nil, rt); err != nil {
return fmt.Errorf("error getting route info: %s", err)
}
@@ -328,15 +337,16 @@ func (a *routesCmd) postRoute(c *cli.Context, appName string, rt *fnmodels.Route
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.PostAppsAppRoutesBadRequest:
return fmt.Errorf("error: %s", err.(*apiroutes.PostAppsAppRoutesBadRequest).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.PostAppsAppRoutesConflict:
return fmt.Errorf("error: %s", err.(*apiroutes.PostAppsAppRoutesConflict).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.PostAppsAppRoutesDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.PostAppsAppRoutesDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
fmt.Println(resp.Payload.Route.Path, "created with", resp.Payload.Route.Image)
@@ -352,15 +362,16 @@ func (a *routesCmd) patchRoute(c *cli.Context, appName, routePath string, r *fnm
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.PatchAppsAppRoutesRouteBadRequest:
return fmt.Errorf("error: %s", err.(*apiroutes.PatchAppsAppRoutesRouteBadRequest).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.PatchAppsAppRoutesRouteNotFound:
return fmt.Errorf("error: %s", err.(*apiroutes.PatchAppsAppRoutesRouteNotFound).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.PatchAppsAppRoutesRouteDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.PatchAppsAppRoutesRouteDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
return nil
@@ -374,13 +385,14 @@ func (a *routesCmd) putRoute(c *cli.Context, appName, routePath string, r *fnmod
Body: &fnmodels.RouteWrapper{Route: r},
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.PutAppsAppRoutesRouteBadRequest:
return fmt.Errorf("error: %s", err.(*apiroutes.PutAppsAppRoutesRouteBadRequest).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.PutAppsAppRoutesRouteDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.PutAppsAppRoutesRouteDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
return nil
}
@@ -392,7 +404,7 @@ func (a *routesCmd) update(c *cli.Context) error {
rt := &fnmodels.Route{}
if !c.Bool("ignore-fn-file") {
if err := routeWithFuncFile(c, nil, rt); err != nil {
if err := routeWithFuncFile(nil, rt); err != nil {
return fmt.Errorf("error updating route: %s", err)
}
}
@@ -461,13 +473,14 @@ func (a *routesCmd) inspect(c *cli.Context) error {
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.GetAppsAppRoutesRouteNotFound:
return fmt.Errorf("error: %s", err.(*apiroutes.GetAppsAppRoutesRouteNotFound).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.GetAppsAppRoutesRouteDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.GetAppsAppRoutesRouteDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
enc := json.NewEncoder(os.Stdout)
@@ -508,13 +521,14 @@ func (a *routesCmd) delete(c *cli.Context) error {
Route: route,
})
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *apiroutes.DeleteAppsAppRoutesRouteNotFound:
return fmt.Errorf("error: %s", err.(*apiroutes.DeleteAppsAppRoutesRouteNotFound).Payload.Error.Message)
return fmt.Errorf("error: %s", e.Payload.Error.Message)
case *apiroutes.DeleteAppsAppRoutesRouteDefault:
return fmt.Errorf("unexpected error: %s", err.(*apiroutes.DeleteAppsAppRoutesRouteDefault).Payload.Error.Message)
return fmt.Errorf("unexpected error: %s", e.Payload.Error.Message)
default:
return fmt.Errorf("unexpected error: %v", err)
}
return fmt.Errorf("unexpected error: %s", err)
}
fmt.Println(appName, route, "deleted")

View File

@@ -176,7 +176,7 @@ func runff(ff *funcfile, stdin io.Reader, stdout, stderr io.Writer, method strin
stdin = strings.NewReader(body)
}
sh = append(sh, ff.FullName())
sh = append(sh, ff.ImageName())
cmd := exec.Command(sh[0], sh[1:]...)
cmd.Stdin = stdin
cmd.Stdout = stdout

View File

@@ -95,7 +95,7 @@ func (t *testcmd) test(c *cli.Context) error {
fmt.Printf("Running %v tests...", len(tests))
target := ff.FullName()
target := ff.ImageName()
runtest := runlocaltest
if t.remote != "" {
if ff.Path == "" {
@@ -116,7 +116,7 @@ func (t *testcmd) test(c *cli.Context) error {
}
errorCount := 0
fmt.Println("running tests on", ff.FullName(), ":")
fmt.Println("running tests on", ff.ImageName(), ":")
for i, tt := range tests {
fmt.Printf("\nTest %v\n", i+1)
start := time.Now()