github.com/devcamcar/cli@v0.0.0-20181107134215-706a05759d18/objects/fn/fns.go (about)

     1  package fn
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"strings"
    11  	"text/tabwriter"
    12  
    13  	"github.com/fnproject/cli/common"
    14  	"github.com/fnproject/cli/objects/app"
    15  	"github.com/fnproject/fn_go/clientv2"
    16  	apifns "github.com/fnproject/fn_go/clientv2/fns"
    17  	models "github.com/fnproject/fn_go/modelsv2"
    18  	"github.com/fnproject/fn_go/provider"
    19  	"github.com/jmoiron/jsonq"
    20  	"github.com/urfave/cli"
    21  )
    22  
    23  type fnsCmd struct {
    24  	provider provider.Provider
    25  	client   *clientv2.Fn
    26  }
    27  
    28  // FnFlags used to create/update functions
    29  var FnFlags = []cli.Flag{
    30  	cli.Uint64Flag{
    31  		Name:  "memory,m",
    32  		Usage: "Memory in MiB",
    33  	},
    34  	cli.StringSliceFlag{
    35  		Name:  "config,c",
    36  		Usage: "Function configuration",
    37  	},
    38  	cli.StringFlag{
    39  		Name:  "format,f",
    40  		Usage: "Hot container IO format - can be one of: default, http, json or cloudevent (check FDK docs to see which are supported for the FDK in use.)",
    41  	},
    42  	cli.IntFlag{
    43  		Name:  "timeout",
    44  		Usage: "Function timeout (eg. 30)",
    45  	},
    46  	cli.IntFlag{
    47  		Name:  "idle-timeout",
    48  		Usage: "Function idle timeout (eg. 30)",
    49  	},
    50  	cli.StringSliceFlag{
    51  		Name:  "annotation",
    52  		Usage: "Function annotation (can be specified multiple times)",
    53  	},
    54  }
    55  var updateFnFlags = FnFlags
    56  
    57  // WithSlash appends "/" to function path
    58  func WithSlash(p string) string {
    59  	p = path.Clean(p)
    60  
    61  	if !strings.HasPrefix(p, "/") {
    62  		p = "/" + p
    63  	}
    64  	return p
    65  }
    66  
    67  // WithoutSlash removes "/" from function path
    68  func WithoutSlash(p string) string {
    69  	p = path.Clean(p)
    70  	p = strings.TrimPrefix(p, "/")
    71  	return p
    72  }
    73  
    74  func printFunctions(c *cli.Context, fns []*models.Fn) error {
    75  	outputFormat := strings.ToLower(c.String("output"))
    76  	if outputFormat == "json" {
    77  		var newFns []interface{}
    78  		for _, fn := range fns {
    79  			newFns = append(newFns, struct {
    80  				Name  string `json:"name"`
    81  				Image string `json:"image"`
    82  				ID string `json:"id"`
    83  			}{
    84  				fn.Name,
    85  				fn.Image,
    86  				fn.ID,
    87  			})
    88  		}
    89  		b, err := json.MarshalIndent(newFns, "", "    ")
    90  		if err != nil {
    91  			return err
    92  		}
    93  		fmt.Fprint(os.Stdout, string(b))
    94  	} else {
    95  		w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
    96  		fmt.Fprint(w, "NAME", "\t", "IMAGE", "\t", "ID", "\n")
    97  
    98  		for _, f := range fns {
    99  			fmt.Fprint(w, f.Name, "\t", f.Image, "\t", f.ID, "\t", "\n")
   100  		}
   101  		if err := w.Flush(); err != nil {
   102  			return err
   103  		}
   104  	}
   105  	return nil
   106  }
   107  
   108  func (f *fnsCmd) list(c *cli.Context) error {
   109  	appName := c.Args().Get(0)
   110  
   111  	a, err := app.GetAppByName(f.client, appName)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	params := &apifns.ListFnsParams{
   116  		Context: context.Background(),
   117  		AppID:   &a.ID,
   118  	}
   119  
   120  	var resFns []*models.Fn
   121  	for {
   122  		resp, err := f.client.Fns.ListFns(params)
   123  
   124  		if err != nil {
   125  			return err
   126  		}
   127  		n := c.Int64("n")
   128  		if n < 0 {
   129  			return errors.New("number of calls: negative value not allowed")
   130  		}
   131  
   132  		resFns = append(resFns, resp.Payload.Items...)
   133  		howManyMore := n - int64(len(resFns)+len(resp.Payload.Items))
   134  		if howManyMore <= 0 || resp.Payload.NextCursor == "" {
   135  			break
   136  		}
   137  
   138  		params.Cursor = &resp.Payload.NextCursor
   139  	}
   140  
   141  	if len(resFns) == 0 {
   142  		fmt.Fprintf(os.Stderr, "No functions found for app: %s\n", appName)
   143  		return nil
   144  	}
   145  
   146  	return printFunctions(c, resFns)
   147  }
   148  
   149  // WithFlags returns a function with specified flags
   150  func WithFlags(c *cli.Context, fn *models.Fn) {
   151  	if i := c.String("image"); i != "" {
   152  		fn.Image = i
   153  	}
   154  	if f := c.String("format"); f != "" {
   155  		fn.Format = f
   156  	}
   157  
   158  	if m := c.Uint64("memory"); m > 0 {
   159  		fn.Memory = m
   160  	}
   161  
   162  	fn.Config = common.ExtractConfig(c.StringSlice("config"))
   163  
   164  	if len(c.StringSlice("annotation")) > 0 {
   165  		fn.Annotations = common.ExtractAnnotations(c)
   166  	}
   167  	if t := c.Int("timeout"); t > 0 {
   168  		to := int32(t)
   169  		fn.Timeout = &to
   170  	}
   171  	if t := c.Int("idle-timeout"); t > 0 {
   172  		to := int32(t)
   173  		fn.IDLETimeout = &to
   174  	}
   175  }
   176  
   177  // WithFuncFileV20180708 used when creating a function from a funcfile
   178  func WithFuncFileV20180708(ff *common.FuncFileV20180708, fn *models.Fn) error {
   179  	var err error
   180  	if ff == nil {
   181  		_, ff, err = common.LoadFuncFileV20180708(".")
   182  		if err != nil {
   183  			return err
   184  		}
   185  	}
   186  	if ff.ImageNameV20180708() != "" { // args take precedence
   187  		fn.Image = ff.ImageNameV20180708()
   188  	}
   189  
   190  	if ff.Format != "" {
   191  		fn.Format = ff.Format
   192  	}
   193  
   194  	if ff.Timeout != nil {
   195  		fn.Timeout = ff.Timeout
   196  	}
   197  	if ff.Memory != 0 {
   198  		fn.Memory = ff.Memory
   199  	}
   200  	if ff.IDLE_timeout != nil {
   201  		fn.IDLETimeout = ff.IDLE_timeout
   202  	}
   203  
   204  	if len(ff.Config) != 0 {
   205  		fn.Config = ff.Config
   206  	}
   207  	if len(ff.Annotations) != 0 {
   208  		fn.Annotations = ff.Annotations
   209  	}
   210  	// do something with triggers here
   211  
   212  	return nil
   213  }
   214  
   215  func (f *fnsCmd) create(c *cli.Context) error {
   216  	appName := c.Args().Get(0)
   217  	fnName := c.Args().Get(1)
   218  
   219  	fn := &models.Fn{}
   220  	fn.Name = fnName
   221  	fn.Image = c.Args().Get(2)
   222  
   223  	WithFlags(c, fn)
   224  
   225  	if fn.Name == "" {
   226  		return errors.New("fnName path is missing")
   227  	}
   228  	if fn.Image == "" {
   229  		return errors.New("no image specified")
   230  	}
   231  
   232  	return CreateFn(f.client, appName, fn)
   233  }
   234  
   235  // CreateFn request
   236  func CreateFn(r *clientv2.Fn, appName string, fn *models.Fn) error {
   237  	a, err := app.GetAppByName(r, appName)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	fn.AppID = a.ID
   243  	err = common.ValidateTagImageName(fn.Image)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	resp, err := r.Fns.CreateFn(&apifns.CreateFnParams{
   249  		Context: context.Background(),
   250  		Body:    fn,
   251  	})
   252  
   253  	if err != nil {
   254  		switch e := err.(type) {
   255  		case *apifns.CreateFnBadRequest:
   256  			return fmt.Errorf("%s", e.Payload.Message)
   257  		case *apifns.CreateFnConflict:
   258  			return fmt.Errorf("%s", e.Payload.Message)
   259  		default:
   260  			return err
   261  		}
   262  	}
   263  
   264  	fmt.Println("Successfully created function:", resp.Payload.Name, "with", resp.Payload.Image)
   265  	return nil
   266  }
   267  
   268  // PutFn updates the fn with the given ID using the content of the provided fn
   269  func PutFn(f *clientv2.Fn, fnID string, fn *models.Fn) error {
   270  	if fn.Image != "" {
   271  		err := common.ValidateTagImageName(fn.Image)
   272  		if err != nil {
   273  			return err
   274  		}
   275  	}
   276  
   277  	_, err := f.Fns.UpdateFn(&apifns.UpdateFnParams{
   278  		Context: context.Background(),
   279  		FnID:    fnID,
   280  		Body:    fn,
   281  	})
   282  
   283  	if err != nil {
   284  		switch e := err.(type) {
   285  		case *apifns.UpdateFnBadRequest:
   286  			return fmt.Errorf("%s", e.Payload.Message)
   287  
   288  		default:
   289  			return err
   290  		}
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // GetFnByName looks up a fn by name using the given client
   297  func GetFnByName(client *clientv2.Fn, appID, fnName string) (*models.Fn, error) {
   298  	resp, err := client.Fns.ListFns(&apifns.ListFnsParams{
   299  		Context: context.Background(),
   300  		AppID:   &appID,
   301  		Name:    &fnName,
   302  	})
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	var fn *models.Fn
   308  	for i := 0; i < len(resp.Payload.Items); i++ {
   309  		if resp.Payload.Items[i].Name == fnName {
   310  			fn = resp.Payload.Items[i]
   311  		}
   312  	}
   313  	if fn == nil {
   314  		return nil, fmt.Errorf("function %s not found", fnName)
   315  	}
   316  
   317  	return fn, nil
   318  }
   319  
   320  func (f *fnsCmd) update(c *cli.Context) error {
   321  	appName := c.Args().Get(0)
   322  	fnName := c.Args().Get(1)
   323  
   324  	app, err := app.GetAppByName(f.client, appName)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	fn, err := GetFnByName(f.client, app.ID, fnName)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	WithFlags(c, fn)
   334  
   335  	err = PutFn(f.client, fn.ID, fn)
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	fmt.Println(appName, fnName, "updated")
   341  	return nil
   342  }
   343  
   344  func (f *fnsCmd) setConfig(c *cli.Context) error {
   345  	appName := c.Args().Get(0)
   346  	fnName := WithoutSlash(c.Args().Get(1))
   347  	key := c.Args().Get(2)
   348  	value := c.Args().Get(3)
   349  
   350  	app, err := app.GetAppByName(f.client, appName)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	fn, err := GetFnByName(f.client, app.ID, fnName)
   355  	if err != nil {
   356  		return err
   357  	}
   358  
   359  	fn.Config = make(map[string]string)
   360  	fn.Config[key] = value
   361  
   362  	if err = PutFn(f.client, fn.ID, fn); err != nil {
   363  		return fmt.Errorf("Error updating function configuration: %v", err)
   364  	}
   365  
   366  	fmt.Println(appName, fnName, "updated", key, "with", value)
   367  	return nil
   368  }
   369  
   370  func (f *fnsCmd) getConfig(c *cli.Context) error {
   371  	appName := c.Args().Get(0)
   372  	fnName := c.Args().Get(1)
   373  	key := c.Args().Get(2)
   374  
   375  	app, err := app.GetAppByName(f.client, appName)
   376  	if err != nil {
   377  		return err
   378  	}
   379  	fn, err := GetFnByName(f.client, app.ID, fnName)
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	val, ok := fn.Config[key]
   385  	if !ok {
   386  		return fmt.Errorf("config key does not exist")
   387  	}
   388  
   389  	fmt.Println(val)
   390  
   391  	return nil
   392  }
   393  
   394  func (f *fnsCmd) listConfig(c *cli.Context) error {
   395  	appName := c.Args().Get(0)
   396  	fnName := c.Args().Get(1)
   397  
   398  	app, err := app.GetAppByName(f.client, appName)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	fn, err := GetFnByName(f.client, app.ID, fnName)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	if len(fn.Config) == 0 {
   408  		fmt.Fprintf(os.Stderr, "No config found for function: %s\n", fnName)
   409  		return nil
   410  	}
   411  
   412  	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
   413  	fmt.Fprint(w, "KEY", "\t", "VALUE", "\n")
   414  	for key, val := range fn.Config {
   415  		fmt.Fprint(w, key, "\t", val, "\n")
   416  	}
   417  	w.Flush()
   418  
   419  	return nil
   420  }
   421  
   422  func (f *fnsCmd) unsetConfig(c *cli.Context) error {
   423  	appName := c.Args().Get(0)
   424  	fnName := WithoutSlash(c.Args().Get(1))
   425  	key := c.Args().Get(2)
   426  
   427  	app, err := app.GetAppByName(f.client, appName)
   428  	if err != nil {
   429  		return err
   430  	}
   431  	fn, err := GetFnByName(f.client, app.ID, fnName)
   432  	if err != nil {
   433  		return err
   434  	}
   435  	fn.Config[key] = ""
   436  
   437  	err = PutFn(f.client, fn.ID, fn)
   438  	if err != nil {
   439  		return err
   440  	}
   441  
   442  	fmt.Printf("removed key '%s' from the function '%s' \n", key, fnName)
   443  	return nil
   444  }
   445  
   446  func (f *fnsCmd) inspect(c *cli.Context) error {
   447  	appName := c.Args().Get(0)
   448  	fnName := WithoutSlash(c.Args().Get(1))
   449  	prop := c.Args().Get(2)
   450  
   451  
   452  	app, err := app.GetAppByName(f.client, appName)
   453  	if err != nil {
   454  		return err
   455  	}
   456  	fn, err := GetFnByName(f.client, app.ID, fnName)
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	if c.Bool("endpoint") {
   462  		 endpoint,ok:= fn.Annotations["fnproject.io/fn/invokeEndpoint"].(string)
   463  		 if !ok {
   464  		 	return errors.New("missing or invalid endpoint on function")
   465  		 }
   466  		 fmt.Println(endpoint)
   467  		 return nil
   468  	}
   469  
   470  	enc := json.NewEncoder(os.Stdout)
   471  	enc.SetIndent("", "\t")
   472  
   473  	if prop == "" {
   474  		enc.Encode(fn)
   475  		return nil
   476  	}
   477  
   478  	data, err := json.Marshal(fn)
   479  	if err != nil {
   480  		return fmt.Errorf("failed to inspect %s: %s", fnName, err)
   481  	}
   482  	var inspect map[string]interface{}
   483  	err = json.Unmarshal(data, &inspect)
   484  	if err != nil {
   485  		return fmt.Errorf("failed to inspect %s: %s", fnName, err)
   486  	}
   487  
   488  	jq := jsonq.NewQuery(inspect)
   489  	field, err := jq.Interface(strings.Split(prop, ".")...)
   490  	if err != nil {
   491  		return errors.New("failed to inspect that function's field")
   492  	}
   493  	enc.Encode(field)
   494  
   495  	return nil
   496  }
   497  
   498  func (f *fnsCmd) delete(c *cli.Context) error {
   499  	appName := c.Args().Get(0)
   500  	fnName := c.Args().Get(1)
   501  
   502  	app, err := app.GetAppByName(f.client, appName)
   503  	if err != nil {
   504  		return err
   505  	}
   506  	fn, err := GetFnByName(f.client, app.ID, fnName)
   507  	if err != nil {
   508  		return err
   509  	}
   510  
   511  	params := apifns.NewDeleteFnParams()
   512  	params.FnID = fn.ID
   513  	_, err = f.client.Fns.DeleteFn(params)
   514  
   515  	if err != nil {
   516  		return err
   517  	}
   518  
   519  	fmt.Println(appName, fnName, "deleted")
   520  	return nil
   521  }