github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/fn/commands/init.go (about)

     1  package commands
     2  
     3  /*
     4  usage: fn init <name>
     5  
     6  If there's a Dockerfile found, this will generate the basic file with just the image name. exit
     7  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.
     8  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.
     9  
    10  */
    11  
    12  import (
    13  	"errors"
    14  	"fmt"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"github.com/iron-io/functions/fn/common"
    20  	"github.com/iron-io/functions/fn/langs"
    21  	"github.com/urfave/cli"
    22  )
    23  
    24  var (
    25  	fileExtToRuntime = map[string]string{
    26  		".go":   "go",
    27  		".js":   "node",
    28  		".rb":   "ruby",
    29  		".py":   "python",
    30  		".rs":   "rust",
    31  		".cs":   "dotnet",
    32  		".fs":   "dotnet",
    33  		".java": "java",
    34  	}
    35  
    36  	filenames = []string{
    37  		"func",
    38  		"Func",
    39  	}
    40  
    41  	fnInitRuntimes []string
    42  )
    43  
    44  func init() {
    45  	for rt := range fileExtToRuntime {
    46  		fnInitRuntimes = append(fnInitRuntimes, rt)
    47  	}
    48  }
    49  
    50  type initFnCmd struct {
    51  	name           string
    52  	force          bool
    53  	runtime        string
    54  	entrypoint     string
    55  	cmd            string
    56  	format         string
    57  	maxConcurrency int
    58  }
    59  
    60  func InitFn() cli.Command {
    61  	a := initFnCmd{}
    62  
    63  	return cli.Command{
    64  		Name:        "init",
    65  		Usage:       "create a local func.yaml file",
    66  		Description: "Creates a func.yaml file in the current directory.  ",
    67  		ArgsUsage:   "<DOCKERHUB_USERNAME/FUNCTION_NAME>",
    68  		Action:      a.init,
    69  		Flags: []cli.Flag{
    70  			cli.BoolFlag{
    71  				Name:        "force, f",
    72  				Usage:       "overwrite existing func.yaml",
    73  				Destination: &a.force,
    74  			},
    75  			cli.StringFlag{
    76  				Name:        "runtime",
    77  				Usage:       "choose an existing runtime - " + strings.Join(fnInitRuntimes, ", "),
    78  				Destination: &a.runtime,
    79  			},
    80  			cli.StringFlag{
    81  				Name:        "entrypoint",
    82  				Usage:       "entrypoint is the command to run to start this function - equivalent to Dockerfile ENTRYPOINT.",
    83  				Destination: &a.entrypoint,
    84  			},
    85  			cli.StringFlag{
    86  				Name:        "format",
    87  				Usage:       "hot function IO format - json or http",
    88  				Destination: &a.format,
    89  				Value:       "",
    90  			},
    91  			cli.IntFlag{
    92  				Name:        "max-concurrency",
    93  				Usage:       "maximum concurrency for hot function",
    94  				Destination: &a.maxConcurrency,
    95  				Value:       1,
    96  			},
    97  			cli.StringFlag{
    98  				Name:        "cmd",
    99  				Usage:       "command",
   100  				Destination: &a.cmd,
   101  				Value:       "",
   102  			},
   103  		},
   104  	}
   105  }
   106  
   107  func (a *initFnCmd) init(c *cli.Context) error {
   108  	if !a.force {
   109  		ff, err := common.LoadFuncfile()
   110  		if _, ok := err.(*common.NotFoundError); !ok && err != nil {
   111  			return err
   112  		}
   113  		if ff != nil {
   114  			return errors.New("function file already exists")
   115  		}
   116  	}
   117  
   118  	err := a.buildFuncFile(c)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	var ffmt *string
   124  	if a.format != "" {
   125  		ffmt = &a.format
   126  	}
   127  
   128  	ff := &common.Funcfile{
   129  		Name:           a.name,
   130  		Runtime:        &a.runtime,
   131  		Version:        common.INITIAL_VERSION,
   132  		Entrypoint:     a.entrypoint,
   133  		Cmd:            a.cmd,
   134  		Format:         ffmt,
   135  		MaxConcurrency: &a.maxConcurrency,
   136  	}
   137  
   138  	_, path := common.AppNamePath(ff.FullName())
   139  	ff.Path = &path
   140  
   141  	if err := common.EncodeFuncfileYAML("func.yaml", ff); err != nil {
   142  		return err
   143  	}
   144  
   145  	fmt.Println("func.yaml created.")
   146  	return nil
   147  }
   148  
   149  func (a *initFnCmd) buildFuncFile(c *cli.Context) error {
   150  	pwd, err := os.Getwd()
   151  	if err != nil {
   152  		return fmt.Errorf("error detecting current working directory: %s", err)
   153  	}
   154  
   155  	a.name = c.Args().First()
   156  	if a.name == "" || strings.Contains(a.name, ":") {
   157  		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>")
   158  	}
   159  
   160  	if common.Exists("Dockerfile") {
   161  		fmt.Println("Dockerfile found, will use that to build.")
   162  		return nil
   163  	}
   164  
   165  	var rt string
   166  	if a.runtime == "" {
   167  		rt, err = detectRuntime(pwd)
   168  		if err != nil {
   169  			return err
   170  		}
   171  		a.runtime = rt
   172  		fmt.Printf("assuming %v runtime\n", rt)
   173  	}
   174  	fmt.Println("runtime:", a.runtime)
   175  	if _, ok := common.AcceptableFnRuntimes[a.runtime]; !ok {
   176  		return fmt.Errorf("init does not support the %s runtime, you'll have to create your own Dockerfile for this function", a.runtime)
   177  	}
   178  
   179  	helper := langs.GetLangHelper(a.runtime)
   180  	if helper == nil {
   181  		fmt.Printf("No helper found for %s runtime, you'll have to pass in the appropriate flags or use a Dockerfile.", a.runtime)
   182  	}
   183  
   184  	if a.entrypoint == "" {
   185  		if helper != nil {
   186  			a.entrypoint = helper.Entrypoint()
   187  		}
   188  	}
   189  	if a.cmd == "" {
   190  		if helper != nil {
   191  			a.cmd = helper.Cmd()
   192  		}
   193  	}
   194  	if a.entrypoint == "" && a.cmd == "" {
   195  		return fmt.Errorf("could not detect entrypoint or cmd for %v, use --entrypoint and/or --cmd to set them explicitly", a.runtime)
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  func detectRuntime(path string) (runtime string, err error) {
   202  	for ext, runtime := range fileExtToRuntime {
   203  		for _, filename := range filenames {
   204  			fn := filepath.Join(path, fmt.Sprintf("%s%s", filename, ext))
   205  			if common.Exists(fn) {
   206  				return runtime, nil
   207  			}
   208  		}
   209  	}
   210  	return "", fmt.Errorf("no supported files found to guess runtime, please set runtime explicitly with --runtime flag")
   211  }