From d3f349804eef7f2acdd1455d7d2adea914c6c525 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Fri, 18 Aug 2017 16:27:11 -0700 Subject: [PATCH 1/2] Fixes #220, uses FN_REGISTRY and --registry flag on push and deploy. Also, cleanup. --- README.md | 37 ++++++++------- cli/Makefile | 5 ++ cli/README.md | 4 +- cli/build.go | 6 +-- cli/bump.go | 3 +- cli/client/api.go | 8 +++- cli/common.go | 61 +++++++++++++++++-------- cli/deploy.go | 21 ++++++--- cli/funcfile.go | 114 +++++++++++++++------------------------------- cli/init.go | 88 +++++++++++++++++------------------ cli/lambda.go | 2 +- cli/main.go | 2 +- cli/push.go | 18 ++++++-- cli/routes.go | 4 +- cli/run.go | 2 +- cli/testfn.go | 4 +- 16 files changed, 192 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index 1cb6058f2..fb4092244 100644 --- a/README.md +++ b/README.md @@ -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 /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= + # Deploy your functions to the Fn server (default localhost:8080) # This will create a route to your function as well fn deploy myapp @@ -83,26 +88,27 @@ 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 -- TODO: Slack or Discord community -- Learn how to [contribute](CONTRIBUTING.md) -- See [milestones](https://github.com/fnproject/fn/milestones) for detailed issues - +* TODO: Slack or Discord community +* Learn how to [contribute](CONTRIBUTING.md) +* See [milestones](https://github.com/fnproject/fn/milestones) for detailed issues ## User Interface @@ -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! diff --git a/cli/Makefile b/cli/Makefile index a0042906d..2986e06ba 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -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 diff --git a/cli/README.md b/cli/README.md index b7bdfa868..bb85411ae 100644 --- a/cli/README.md +++ b/cli/README.md @@ -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 / +fn init [] ``` 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"] / +fn init [--runtime node] [--entrypoint "node hello.js"] [] ``` Or, if you want full control, just make a Dockerfile. If `init` finds a Dockerfile, it will use that instead of runtime and entrypoint. diff --git a/cli/build.go b/cli/build.go index 2c4512ad4..636f8d323 100644 --- a/cli/build.go +++ b/cli/build.go @@ -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 } diff --git a/cli/bump.go b/cli/bump.go index f555daf13..2b6129169 100644 --- a/cli/bump.go +++ b/cli/bump.go @@ -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 { diff --git a/cli/client/api.go b/cli/client/api.go index 7360b9ba0..4256e7811 100644 --- a/cli/client/api.go +++ b/cli/client/api.go @@ -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 diff --git a/cli/common.go b/cli/common.go index bcde45957..7a88d7abc 100644 --- a/cli/common.go +++ b/cli/common.go @@ -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 { diff --git a/cli/deploy.go b/cli/deploy.go index 44c102d4a..e06043563 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -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,7 +159,7 @@ 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) } diff --git a/cli/funcfile.go b/cli/funcfile.go index 9977d7f1b..cdc082b04 100644 --- a/cli/funcfile.go +++ b/cli/funcfile.go @@ -9,7 +9,6 @@ import ( "path/filepath" "strings" - fnmodels "github.com/funcy/functions_go/models" yaml "gopkg.in/yaml.v2" ) @@ -39,43 +38,9 @@ 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"` - 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"` -} - -func (ff *funcfile) FullName() string { - fname := ff.Name - if ff.Version != "" { - fname = fmt.Sprintf("%s:%s", fname, ff.Version) - } - return fname -} - -func (ff *funcfile) RuntimeTag() (runtime, tag string) { - if ff.Runtime == nil { - return "", "" - } - - rt := *ff.Runtime - tagpos := strings.Index(rt, ":") - if tagpos == -1 { - return rt, "" - } - - 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"` + 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"` @@ -91,44 +56,36 @@ type flatfuncfile struct { 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 (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) + } + return fname } -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, +func (ff *funcfile) RuntimeTag() (runtime, tag string) { + if ff.Runtime == "" { + return "", "" } - 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 + + rt := ff.Runtime + tagpos := strings.Index(rt, ":") + if tagpos == -1 { + return rt, "" + } + + return rt[:tagpos], rt[tagpos+1:] } func findFuncfile(path string) (string, error) { @@ -176,9 +133,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 +145,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 +156,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) } diff --git a/cli/init.go b/cli/init.go index b51d6f9f7..6f85e8ef4 100644 --- a/cli/init.go +++ b/cli/init.go @@ -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: "", + 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 /.\nTry: fn init /") + a.Name = c.Args().First() + // if a.name == "" { + // // return errors.New("please specify a name for your function.\nTry: fn init ") + // } 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") } diff --git a/cli/lambda.go b/cli/lambda.go index 0c966bad6..d254e042e 100644 --- a/cli/lambda.go +++ b/cli/lambda.go @@ -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 diff --git a/cli/main.go b/cli/main.go index ebd8ae484..e958084f3 100644 --- a/cli/main.go +++ b/cli/main.go @@ -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) } } diff --git a/cli/push.go b/cli/push.go index 92b33d98d..e6ded72eb 100644 --- a/cli/push.go +++ b/cli/push.go @@ -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 } diff --git a/cli/routes.go b/cli/routes.go index 13240c0ac..164c2cf24 100644 --- a/cli/routes.go +++ b/cli/routes.go @@ -268,8 +268,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 diff --git a/cli/run.go b/cli/run.go index 3ffc2ef9d..8a9e99778 100644 --- a/cli/run.go +++ b/cli/run.go @@ -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 diff --git a/cli/testfn.go b/cli/testfn.go index e83bb0f6c..5b4fd85c3 100644 --- a/cli/testfn.go +++ b/cli/testfn.go @@ -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() From c01eead0832dab4f1ca789e62287a03e8445a5bd Mon Sep 17 00:00:00 2001 From: Denis Makogon Date: Tue, 22 Aug 2017 21:08:18 +0300 Subject: [PATCH 2/2] Improve CLI core --- cli/apps.go | 49 +++++++++++++++++--------------- cli/calls.go | 16 +++++------ cli/deploy.go | 2 +- cli/funcfile.go | 15 +++++----- cli/logs.go | 8 +++--- cli/routes.go | 74 +++++++++++++++++++++++++++++-------------------- 6 files changed, 91 insertions(+), 73 deletions(-) diff --git a/cli/apps.go b/cli/apps.go index b2fe80d71..6ce314459 100644 --- a/cli/apps.go +++ b/cli/apps.go @@ -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) } diff --git a/cli/calls.go b/cli/calls.go index 9cbdfe57c..5e7d76362 100644 --- a/cli/calls.go +++ b/cli/calls.go @@ -64,12 +64,12 @@ func (call *callsCmd) get(ctx *cli.Context) error { } resp, err := call.client.Call.GetAppsAppCallsCall(¶ms) 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(¶ms) 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 diff --git a/cli/deploy.go b/cli/deploy.go index e06043563..498d72e86 100644 --- a/cli/deploy.go +++ b/cli/deploy.go @@ -166,7 +166,7 @@ func (p *deploycmd) route(c *cli.Context, ff *funcfile) error { 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) diff --git a/cli/funcfile.go b/cli/funcfile.go index cdc082b04..0ff936974 100644 --- a/cli/funcfile.go +++ b/cli/funcfile.go @@ -47,13 +47,14 @@ type funcfile struct { 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"` + 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) ImageName() string { diff --git a/cli/logs.go b/cli/logs.go index 676f50aa0..80b2ce01b 100644 --- a/cli/logs.go +++ b/cli/logs.go @@ -41,12 +41,12 @@ func (log *logsCmd) get(ctx *cli.Context) error { } resp, err := log.client.Operations.GetAppsAppCallsCallLog(¶ms) 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 diff --git a/cli/routes.go b/cli/routes.go index 164c2cf24..ffdcd4764 100644 --- a/cli/routes.go +++ b/cli/routes.go @@ -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() @@ -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")