github.com/devcamcar/cli@v0.0.0-20181107134215-706a05759d18/common/funcfile.go (about)

     1  package common
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/fnproject/cli/config"
    12  	"github.com/spf13/viper"
    13  
    14  	yaml "gopkg.in/yaml.v2"
    15  )
    16  
    17  var (
    18  	validFuncfileNames = [...]string{
    19  		"func.yaml",
    20  		"func.yml",
    21  		"func.json",
    22  	}
    23  )
    24  
    25  // InputMap to be used within FFTest.
    26  type InputMap struct {
    27  	Body interface{}
    28  }
    29  
    30  // OutputMap to be used within FFTest.
    31  type OutputMap struct {
    32  	Body interface{}
    33  }
    34  
    35  // FFTest represents a test for a funcfile.
    36  type FFTest struct {
    37  	Name   string     `yaml:"name,omitempty" json:"name,omitempty"`
    38  	Input  *InputMap  `yaml:"input,omitempty" json:"input,omitempty"`
    39  	Output *OutputMap `yaml:"outoutput,omitempty" json:"output,omitempty"`
    40  	Err    *string    `yaml:"err,omitempty" json:"err,omitempty"`
    41  	// Env    map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
    42  }
    43  
    44  type inputVar struct {
    45  	Name     string `yaml:"name" json:"name"`
    46  	Required bool   `yaml:"required" json:"required"`
    47  }
    48  
    49  // Expects represents expected env vars in funcfile.
    50  type Expects struct {
    51  	Config []inputVar `yaml:"config" json:"config"`
    52  }
    53  
    54  // FuncFile defines the internal structure of a func.yaml/json/yml
    55  type FuncFile struct {
    56  	Name string `yaml:"name,omitempty" json:"name,omitempty"`
    57  
    58  	// Build params
    59  	Version     string   `yaml:"version,omitempty" json:"version,omitempty"`
    60  	Runtime     string   `yaml:"runtime,omitempty" json:"runtime,omitempty"`
    61  	Entrypoint  string   `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
    62  	Cmd         string   `yaml:"cmd,omitempty" json:"cmd,omitempty"`
    63  	Build       []string `yaml:"build,omitempty" json:"build,omitempty"`
    64  	Tests       []FFTest `yaml:"tests,omitempty" json:"tests,omitempty"`
    65  	BuildImage  string   `yaml:"build_image,omitempty" json:"build_image,omitempty"` // Image to use as base for building
    66  	RunImage    string   `yaml:"run_image,omitempty" json:"run_image,omitempty"`     // Image to use for running
    67  	ContentType string   `yaml:"content_type,omitempty" json:"content_type,omitempty"`
    68  
    69  	// Route params
    70  	// TODO embed models.Route
    71  	Type        string                 `yaml:"type,omitempty" json:"type,omitempty"`
    72  	Memory      uint64                 `yaml:"memory,omitempty" json:"memory,omitempty"`
    73  	Cpus        string                 `yaml:"cpus,omitempty" json:"cpus,omitempty"`
    74  	Format      string                 `yaml:"format,omitempty" json:"format,omitempty"`
    75  	Timeout     *int32                 `yaml:"timeout,omitempty" json:"timeout,omitempty"`
    76  	Path        string                 `yaml:"path,omitempty" json:"path,omitempty"`
    77  	Config      map[string]string      `yaml:"config,omitempty" json:"config,omitempty"`
    78  	Headers     map[string][]string    `yaml:"headers,omitempty" json:"headers,omitempty"`
    79  	IDLETimeout *int32                 `yaml:"idle_timeout,omitempty" json:"idle_timeout,omitempty"`
    80  	Annotations map[string]interface{} `yaml:"annotations,omitempty" json:"annotations,omitempty"`
    81  
    82  	// Run/test
    83  	Expects Expects `yaml:"expects,omitempty" json:"expects,omitempty"`
    84  }
    85  
    86  // FuncFileV20180708 defines the latest internal structure of a func.yaml/json/yml
    87  type FuncFileV20180708 struct {
    88  	Schema_version int `yaml:"schema_version,omitempty" json:"schema_version,omitempty"`
    89  
    90  	Name         string `yaml:"name,omitempty" json:"name,omitempty"`
    91  	Version      string `yaml:"version,omitempty" json:"version,omitempty"`
    92  	Runtime      string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
    93  	Build_image  string `yaml:"build_image,omitempty" json:"build_image,omitempty"` // Image to use as base for building
    94  	Run_image    string `yaml:"run_image,omitempty" json:"run_image,omitempty"`     // Image to use for running
    95  	Cmd          string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
    96  	Entrypoint   string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
    97  	Content_type string `yaml:"content_type,omitempty" json:"content_type,omitempty"`
    98  	Format       string `yaml:"format,omitempty" json:"format,omitempty"`
    99  	Type         string `yaml:"type,omitempty" json:"type,omitempty"`
   100  	Memory       uint64 `yaml:"memory,omitempty" json:"memory,omitempty"`
   101  	Timeout      *int32 `yaml:"timeout,omitempty" json:"timeout,omitempty"`
   102  	IDLE_timeout *int32 `yaml:"idle_timeout,omitempty" json:"idle_timeout,omitempty"`
   103  
   104  	Config      map[string]string      `yaml:"config,omitempty" json:"config,omitempty"`
   105  	Annotations map[string]interface{} `yaml:"annotations,omitempty" json:"annotations,omitempty"`
   106  
   107  	Build []string `yaml:"build,omitempty" json:"build,omitempty"`
   108  
   109  	Expects  Expects   `yaml:"expects,omitempty" json:"expects,omitempty"`
   110  	Triggers []Trigger `yaml:"triggers,omitempty" json:"triggers,omitempty"`
   111  }
   112  
   113  // Trigger represents a trigger for a FuncFileV20180708
   114  type Trigger struct {
   115  	Name   string `yaml:"name,omitempty" json:"name,omitempty"`
   116  	Type   string `yaml:"type,omitempty" json:"type,omitempty"`
   117  	Source string `yaml:"source,omitempty" json:"source,omitempty"`
   118  }
   119  
   120  // ImageName returns the name of a funcfile image
   121  func (ff *FuncFile) ImageName() string {
   122  	fname := ff.Name
   123  	if !strings.Contains(fname, "/") {
   124  
   125  		reg := viper.GetString(config.EnvFnRegistry)
   126  		if reg != "" {
   127  			if reg[len(reg)-1] != '/' {
   128  				reg += "/"
   129  			}
   130  			fname = fmt.Sprintf("%s%s", reg, fname)
   131  		}
   132  	}
   133  	if ff.Version != "" {
   134  		fname = fmt.Sprintf("%s:%s", fname, ff.Version)
   135  	}
   136  	return fname
   137  }
   138  
   139  // RuntimeTag returns the runtime and tag.
   140  func (ff *FuncFile) RuntimeTag() (runtime, tag string) {
   141  	if ff.Runtime == "" {
   142  		return "", ""
   143  	}
   144  
   145  	rt := ff.Runtime
   146  	tagpos := strings.Index(rt, ":")
   147  	if tagpos == -1 {
   148  		return rt, ""
   149  	}
   150  
   151  	return rt[:tagpos], rt[tagpos+1:]
   152  }
   153  
   154  // findFuncfile for a func.yaml/json/yml file in path
   155  func FindFuncfile(path string) (string, error) {
   156  	for _, fn := range validFuncfileNames {
   157  		fullfn := filepath.Join(path, fn)
   158  		if Exists(fullfn) {
   159  			return fullfn, nil
   160  		}
   161  	}
   162  	return "", NewNotFoundError("could not find function file")
   163  }
   164  
   165  // FindAndParseFuncfile for a func.yaml/json/yml file.
   166  func FindAndParseFuncfile(path string) (fpath string, ff *FuncFile, err error) {
   167  	fpath, err = FindFuncfile(path)
   168  	if err != nil {
   169  		return "", nil, err
   170  	}
   171  	ff, err = ParseFuncfile(fpath)
   172  	if err != nil {
   173  		return "", nil, err
   174  	}
   175  	return fpath, ff, err
   176  }
   177  
   178  // LoadFuncfile returns a parsed funcfile.
   179  func LoadFuncfile(path string) (string, *FuncFile, error) {
   180  	return FindAndParseFuncfile(path)
   181  }
   182  
   183  // ParseFuncfile check file type to decode and parse.
   184  func ParseFuncfile(path string) (*FuncFile, error) {
   185  	ext := filepath.Ext(path)
   186  	switch ext {
   187  	case ".json":
   188  		return decodeFuncfileJSON(path)
   189  	case ".yaml", ".yml":
   190  		return decodeFuncfileYAML(path)
   191  	}
   192  	return nil, errUnexpectedFileFormat
   193  }
   194  
   195  func storeFuncfile(path string, ff *FuncFile) error {
   196  	ext := filepath.Ext(path)
   197  	switch ext {
   198  	case ".json":
   199  		return encodeFuncfileJSON(path, ff)
   200  	case ".yaml", ".yml":
   201  		return EncodeFuncfileYAML(path, ff)
   202  	}
   203  	return errUnexpectedFileFormat
   204  }
   205  
   206  func decodeFuncfileJSON(path string) (*FuncFile, error) {
   207  	f, err := os.Open(path)
   208  	if err != nil {
   209  		return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
   210  	}
   211  	ff := &FuncFile{}
   212  	// ff.Route = &fnmodels.Route{}
   213  	err = json.NewDecoder(f).Decode(ff)
   214  	// ff := fff.MakeFuncFile()
   215  	return ff, err
   216  }
   217  
   218  func decodeFuncfileYAML(path string) (*FuncFile, error) {
   219  	b, err := ioutil.ReadFile(path)
   220  	if err != nil {
   221  		return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
   222  	}
   223  	ff := &FuncFile{}
   224  	err = yaml.Unmarshal(b, ff)
   225  	// ff := fff.MakeFuncFile()
   226  	return ff, err
   227  }
   228  
   229  func encodeFuncfileJSON(path string, ff *FuncFile) error {
   230  	f, err := os.Open(path)
   231  	if err != nil {
   232  		return fmt.Errorf("could not open %s for encoding. Error: %v", path, err)
   233  	}
   234  	return json.NewEncoder(f).Encode(ff)
   235  }
   236  
   237  // EncodeFuncfileYAML encodes function file.
   238  func EncodeFuncfileYAML(path string, ff *FuncFile) error {
   239  	b, err := yaml.Marshal(ff)
   240  	if err != nil {
   241  		return fmt.Errorf("could not encode function file. Error: %v", err)
   242  	}
   243  	return ioutil.WriteFile(path, b, os.FileMode(0644))
   244  }
   245  
   246  // IsFuncFile check vaid funcfile.
   247  func IsFuncFile(path string, info os.FileInfo) bool {
   248  	if info.IsDir() {
   249  		return false
   250  	}
   251  
   252  	basefn := filepath.Base(path)
   253  	for _, fn := range validFuncfileNames {
   254  		if basefn == fn {
   255  			return true
   256  		}
   257  	}
   258  	return false
   259  }
   260  
   261  // --------- FuncFileV20180708 -------------
   262  
   263  func FindAndParseFuncFileV20180708(path string) (fpath string, ff *FuncFileV20180708, err error) {
   264  	fpath, err = FindFuncfile(path)
   265  	if err != nil {
   266  		return "", nil, err
   267  	}
   268  	ff, err = ParseFuncFileV20180708(fpath)
   269  	if err != nil {
   270  		return "", nil, err
   271  	}
   272  	return fpath, ff, err
   273  }
   274  
   275  func LoadFuncFileV20180708(path string) (string, *FuncFileV20180708, error) {
   276  	return FindAndParseFuncFileV20180708(path)
   277  }
   278  
   279  func ParseFuncFileV20180708(path string) (*FuncFileV20180708, error) {
   280  	ext := filepath.Ext(path)
   281  	switch ext {
   282  	case ".json":
   283  		return decodeFuncFileV20180708JSON(path)
   284  	case ".yaml", ".yml":
   285  		return decodeFuncFileV20180708YAML(path)
   286  	}
   287  	return nil, errUnexpectedFileFormat
   288  }
   289  
   290  func decodeFuncFileV20180708JSON(path string) (*FuncFileV20180708, error) {
   291  	f, err := os.Open(path)
   292  	if err != nil {
   293  		return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
   294  	}
   295  	ff := &FuncFileV20180708{}
   296  	// ff.Route = &fnmodels.Route{}
   297  	err = json.NewDecoder(f).Decode(ff)
   298  	// ff := fff.MakeFuncFile()
   299  	return ff, err
   300  }
   301  
   302  func decodeFuncFileV20180708YAML(path string) (*FuncFileV20180708, error) {
   303  	b, err := ioutil.ReadFile(path)
   304  	if err != nil {
   305  		return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
   306  	}
   307  	ff := &FuncFileV20180708{}
   308  	err = yaml.Unmarshal(b, ff)
   309  	// ff := fff.MakeFuncFile()
   310  	return ff, err
   311  }
   312  
   313  func encodeFuncFileV20180708JSON(path string, ff *FuncFileV20180708) error {
   314  	f, err := os.Open(path)
   315  	if err != nil {
   316  		return fmt.Errorf("could not open %s for encoding. Error: %v", path, err)
   317  	}
   318  	return json.NewEncoder(f).Encode(ff)
   319  }
   320  
   321  // EncodeFuncfileYAML encodes function file.
   322  func EncodeFuncFileV20180708YAML(path string, ff *FuncFileV20180708) error {
   323  	b, err := yaml.Marshal(ff)
   324  	if err != nil {
   325  		return fmt.Errorf("could not encode function file. Error: %v", err)
   326  	}
   327  	return ioutil.WriteFile(path, b, os.FileMode(0644))
   328  }
   329  
   330  func storeFuncFileV20180708(path string, ff *FuncFileV20180708) error {
   331  	ext := filepath.Ext(path)
   332  	switch ext {
   333  	case ".json":
   334  		return encodeFuncFileV20180708JSON(path, ff)
   335  	case ".yaml", ".yml":
   336  		return EncodeFuncFileV20180708YAML(path, ff)
   337  	}
   338  	return errUnexpectedFileFormat
   339  }
   340  
   341  // ImageName returns the name of a funcfile image
   342  func (ff *FuncFileV20180708) ImageNameV20180708() string {
   343  	fname := ff.Name
   344  	if !strings.Contains(fname, "/") {
   345  
   346  		reg := viper.GetString(config.EnvFnRegistry)
   347  		if reg != "" {
   348  			if reg[len(reg)-1] != '/' {
   349  				reg += "/"
   350  			}
   351  			fname = fmt.Sprintf("%s%s", reg, fname)
   352  		}
   353  	}
   354  	if ff.Version != "" {
   355  		fname = fmt.Sprintf("%s:%s", fname, ff.Version)
   356  	}
   357  	return fname
   358  }