mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
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:
166
fnctl/init.go
166
fnctl/init.go
@@ -1,13 +1,24 @@
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/iron-io/functions/fnctl/langs"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
@@ -27,6 +38,7 @@ var (
|
||||
".pl": "perl",
|
||||
".py": "python",
|
||||
".scala": "scala",
|
||||
".rb": "ruby",
|
||||
}
|
||||
|
||||
fnRuntimes []string
|
||||
@@ -39,8 +51,10 @@ func init() {
|
||||
}
|
||||
|
||||
type initFnCmd struct {
|
||||
force bool
|
||||
runtime string
|
||||
name string
|
||||
force bool
|
||||
runtime *string
|
||||
entrypoint *string
|
||||
}
|
||||
|
||||
func initFn() cli.Command {
|
||||
@@ -49,19 +63,24 @@ func initFn() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "init",
|
||||
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.",
|
||||
ArgsUsage: "<entrypoint>",
|
||||
Description: "Creates a function.yaml file in the current directory. ",
|
||||
ArgsUsage: "<DOCKERHUB_USERNAME:FUNCTION_NAME>",
|
||||
Action: a.init,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "f",
|
||||
Name: "force, f",
|
||||
Usage: "overwrite existing function.yaml",
|
||||
Destination: &a.force,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "runtime",
|
||||
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 {
|
||||
if !a.force {
|
||||
for _, fn := range validfn {
|
||||
if _, err := os.Stat(fn); !os.IsNotExist(err) {
|
||||
return errors.New("function file already exists")
|
||||
ff, err := findFuncfile()
|
||||
if err != nil {
|
||||
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()
|
||||
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()
|
||||
err := a.buildFuncFile(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error detecting current working directory: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if a.runtime == "" {
|
||||
rt, err := detectRuntime(pwd)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
/*
|
||||
Now we can make some guesses for the entrypoint based on runtime.
|
||||
If Go, use ./foldername, if ruby, use ruby and a filename. If node, node + filename
|
||||
*/
|
||||
|
||||
ff := &funcfile{
|
||||
Runtime: &a.runtime,
|
||||
Name: a.name,
|
||||
Runtime: a.runtime,
|
||||
Version: initialVersion,
|
||||
Entrypoint: &entrypoint,
|
||||
Entrypoint: a.entrypoint,
|
||||
}
|
||||
|
||||
if err := encodeFuncfileYAML("function.yaml", ff); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("function.yaml written")
|
||||
fmt.Println("function.yaml created.")
|
||||
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{
|
||||
"": 0,
|
||||
}
|
||||
@@ -145,7 +232,6 @@ func detectRuntime(path string) (string, error) {
|
||||
biggest = ext
|
||||
}
|
||||
}
|
||||
|
||||
return biggest, nil
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user