github.com/tonto/cli@v0.0.0-20180104210444-aec958fa47db/routes.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/url"
     9  	"os"
    10  	"path"
    11  	"strings"
    12  	"text/tabwriter"
    13  
    14  	client "github.com/fnproject/cli/client"
    15  	fnclient "github.com/fnproject/fn_go/client"
    16  	apiroutes "github.com/fnproject/fn_go/client/routes"
    17  	fnmodels "github.com/fnproject/fn_go/models"
    18  	"github.com/jmoiron/jsonq"
    19  	"github.com/urfave/cli"
    20  )
    21  
    22  type routesCmd struct {
    23  	client *fnclient.Fn
    24  }
    25  
    26  var routeFlags = []cli.Flag{
    27  	cli.StringFlag{
    28  		Name:  "image,i",
    29  		Usage: "image name",
    30  	},
    31  	cli.Uint64Flag{
    32  		Name:  "memory,m",
    33  		Usage: "memory in MiB",
    34  	},
    35  	cli.StringFlag{
    36  		Name:  "type,t",
    37  		Usage: "route type - sync or async",
    38  	},
    39  	cli.StringSliceFlag{
    40  		Name:  "config,c",
    41  		Usage: "route configuration",
    42  	},
    43  	cli.StringSliceFlag{
    44  		Name:  "headers",
    45  		Usage: "route response headers",
    46  	},
    47  	cli.StringFlag{
    48  		Name:  "format,f",
    49  		Usage: "hot container IO format - default or http",
    50  	},
    51  	cli.IntFlag{
    52  		Name:  "timeout",
    53  		Usage: "route timeout (eg. 30)",
    54  	},
    55  	cli.IntFlag{
    56  		Name:  "idle-timeout",
    57  		Usage: "route idle timeout (eg. 30)",
    58  	},
    59  }
    60  
    61  var updateRouteFlags = routeFlags
    62  
    63  var callFnFlags = append(runflags(),
    64  	cli.BoolFlag{
    65  		Name:  "display-call-id",
    66  		Usage: "whether display call ID or not",
    67  	},
    68  )
    69  
    70  func routes() cli.Command {
    71  
    72  	r := routesCmd{client: client.APIClient()}
    73  
    74  	return cli.Command{
    75  		Name:  "routes",
    76  		Usage: "manage routes",
    77  		Subcommands: []cli.Command{
    78  			{
    79  				Name:      "call",
    80  				Usage:     "call a route",
    81  				ArgsUsage: "<app> </path> [image]",
    82  				Action:    r.call,
    83  				Flags:     callFnFlags,
    84  			},
    85  			{
    86  				Name:      "list",
    87  				Aliases:   []string{"l"},
    88  				Usage:     "list routes for `app`",
    89  				ArgsUsage: "<app>",
    90  				Action:    r.list,
    91  			},
    92  			{
    93  				Name:      "create",
    94  				Aliases:   []string{"c"},
    95  				Usage:     "create a route in an `app`",
    96  				ArgsUsage: "<app> </path>",
    97  				Action:    r.create,
    98  				Flags:     routeFlags,
    99  			},
   100  			{
   101  				Name:      "update",
   102  				Aliases:   []string{"u"},
   103  				Usage:     "update a route in an `app`",
   104  				ArgsUsage: "<app> </path>",
   105  				Action:    r.update,
   106  				Flags:     updateRouteFlags,
   107  			},
   108  			{
   109  				Name:  "config",
   110  				Usage: "operate a route configuration set",
   111  				Subcommands: []cli.Command{
   112  					{
   113  						Name:      "set",
   114  						Aliases:   []string{"s"},
   115  						Usage:     "store a configuration key for this route",
   116  						ArgsUsage: "<app> </path> <key> <value>",
   117  						Action:    r.configSet,
   118  					},
   119  					{
   120  						Name:      "unset",
   121  						Aliases:   []string{"u"},
   122  						Usage:     "remove a configuration key for this route",
   123  						ArgsUsage: "<app> </path> <key>",
   124  						Action:    r.configUnset,
   125  					},
   126  				},
   127  			},
   128  			{
   129  				Name:      "delete",
   130  				Aliases:   []string{"d"},
   131  				Usage:     "delete a route from `app`",
   132  				ArgsUsage: "<app> </path>",
   133  				Action:    r.delete,
   134  			},
   135  			{
   136  				Name:      "inspect",
   137  				Aliases:   []string{"i"},
   138  				Usage:     "retrieve one or all routes properties",
   139  				ArgsUsage: "<app> </path> [property.[key]]",
   140  				Action:    r.inspect,
   141  			},
   142  		},
   143  	}
   144  }
   145  
   146  func call() cli.Command {
   147  	r := routesCmd{client: client.APIClient()}
   148  
   149  	return cli.Command{
   150  		Name:      "call",
   151  		Usage:     "call a remote function",
   152  		ArgsUsage: "<app> </path>",
   153  		Flags:     callFnFlags,
   154  		Action:    r.call,
   155  	}
   156  }
   157  
   158  func cleanRoutePath(p string) string {
   159  	p = path.Clean(p)
   160  	if !path.IsAbs(p) {
   161  		p = "/" + p
   162  	}
   163  	return p
   164  }
   165  
   166  func (a *routesCmd) list(c *cli.Context) error {
   167  	appName := c.Args().Get(0)
   168  
   169  	resp, err := a.client.Routes.GetAppsAppRoutes(&apiroutes.GetAppsAppRoutesParams{
   170  		Context: context.Background(),
   171  		App:     appName,
   172  	})
   173  
   174  	if err != nil {
   175  		switch e := err.(type) {
   176  		case *apiroutes.GetAppsAppRoutesNotFound:
   177  			return fmt.Errorf("%s", e.Payload.Error.Message)
   178  		case *apiroutes.GetAppsAppRoutesDefault:
   179  			return fmt.Errorf("%s", e.Payload.Error.Message)
   180  		default:
   181  			return err
   182  		}
   183  	}
   184  
   185  	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
   186  	fmt.Fprint(w, "path", "\t", "image", "\t", "endpoint", "\n")
   187  	for _, route := range resp.Payload.Routes {
   188  		endpoint := path.Join(client.Host(), "r", appName, route.Path)
   189  		if err != nil {
   190  			return fmt.Errorf("error parsing functions route path: %s", err)
   191  		}
   192  
   193  		fmt.Fprint(w, route.Path, "\t", route.Image, "\t", endpoint, "\n")
   194  	}
   195  	w.Flush()
   196  
   197  	return nil
   198  }
   199  
   200  func (a *routesCmd) call(c *cli.Context) error {
   201  	appName := c.Args().Get(0)
   202  	route := cleanRoutePath(c.Args().Get(1))
   203  
   204  	u := url.URL{
   205  		Scheme: "http",
   206  		Host:   client.Host(),
   207  	}
   208  	u.Path = path.Join(u.Path, "r", appName, route)
   209  	content := stdin()
   210  
   211  	return client.CallFN(u.String(), content, os.Stdout, c.String("method"), c.StringSlice("e"), c.Bool("display-call-id"))
   212  }
   213  
   214  func routeWithFlags(c *cli.Context, rt *fnmodels.Route) {
   215  	if rt.Image == "" {
   216  		if i := c.String("image"); i != "" {
   217  			rt.Image = i
   218  		}
   219  	}
   220  	if rt.Format == "" {
   221  		if f := c.String("format"); f != "" {
   222  			rt.Format = f
   223  		}
   224  	}
   225  	if rt.Type == "" {
   226  		if t := c.String("type"); t != "" {
   227  			rt.Type = t
   228  		}
   229  	}
   230  	if rt.Memory == 0 {
   231  		if m := c.Uint64("memory"); m > 0 {
   232  			rt.Memory = m
   233  		}
   234  	}
   235  	if rt.Timeout == nil {
   236  		if t := c.Int("timeout"); t > 0 {
   237  			to := int32(t)
   238  			rt.Timeout = &to
   239  		}
   240  	}
   241  	if rt.IDLETimeout == nil {
   242  		if t := c.Int("idle-timeout"); t > 0 {
   243  			to := int32(t)
   244  			rt.IDLETimeout = &to
   245  		}
   246  	}
   247  	if len(rt.Headers) == 0 {
   248  		if len(c.StringSlice("headers")) > 0 {
   249  			headers := map[string][]string{}
   250  			for _, header := range c.StringSlice("headers") {
   251  				parts := strings.Split(header, "=")
   252  				headers[parts[0]] = strings.Split(parts[1], ";")
   253  			}
   254  			rt.Headers = headers
   255  		}
   256  	}
   257  	if len(rt.Config) == 0 {
   258  		if len(c.StringSlice("config")) > 0 {
   259  			rt.Config = extractEnvConfig(c.StringSlice("config"))
   260  		}
   261  	}
   262  }
   263  
   264  func routeWithFuncFile(ff *funcfile, rt *fnmodels.Route) error {
   265  	var err error
   266  	if ff == nil {
   267  		_, ff, err = loadFuncfile()
   268  		if err != nil {
   269  			return err
   270  		}
   271  	}
   272  	if ff.ImageName() != "" { // args take precedence
   273  		rt.Image = ff.ImageName()
   274  	}
   275  	if ff.Format != "" {
   276  		rt.Format = ff.Format
   277  	}
   278  	if ff.Timeout != nil {
   279  		rt.Timeout = ff.Timeout
   280  	}
   281  	if rt.Path == "" && ff.Path != "" {
   282  		rt.Path = ff.Path
   283  	}
   284  	if rt.Type == "" && ff.Type != "" {
   285  		rt.Type = ff.Type
   286  	}
   287  	if ff.Memory != 0 {
   288  		rt.Memory = ff.Memory
   289  	}
   290  	if ff.IDLETimeout != nil {
   291  		rt.IDLETimeout = ff.IDLETimeout
   292  	}
   293  	if len(ff.Headers) != 0 {
   294  		rt.Headers = ff.Headers
   295  	}
   296  	if len(ff.Config) != 0 {
   297  		rt.Config = ff.Config
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  func (a *routesCmd) create(c *cli.Context) error {
   304  	appName := c.Args().Get(0)
   305  	route := cleanRoutePath(c.Args().Get(1))
   306  
   307  	rt := &fnmodels.Route{}
   308  	rt.Path = route
   309  	rt.Image = c.Args().Get(2)
   310  
   311  	routeWithFlags(c, rt)
   312  
   313  	if rt.Path == "" {
   314  		return errors.New("route path is missing")
   315  	}
   316  	if rt.Image == "" {
   317  		return errors.New("no image specified")
   318  	}
   319  
   320  	return a.postRoute(c, appName, rt)
   321  }
   322  
   323  func (a *routesCmd) postRoute(c *cli.Context, appName string, rt *fnmodels.Route) error {
   324  
   325  	err := validateImageName(rt.Image)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	body := &fnmodels.RouteWrapper{
   331  		Route: rt,
   332  	}
   333  
   334  	resp, err := a.client.Routes.PostAppsAppRoutes(&apiroutes.PostAppsAppRoutesParams{
   335  		Context: context.Background(),
   336  		App:     appName,
   337  		Body:    body,
   338  	})
   339  
   340  	if err != nil {
   341  		switch e := err.(type) {
   342  		case *apiroutes.PostAppsAppRoutesBadRequest:
   343  			return fmt.Errorf("%s", e.Payload.Error.Message)
   344  		case *apiroutes.PostAppsAppRoutesConflict:
   345  			return fmt.Errorf("%s", e.Payload.Error.Message)
   346  		case *apiroutes.PostAppsAppRoutesDefault:
   347  			return fmt.Errorf("%s", e.Payload.Error.Message)
   348  		default:
   349  			return fmt.Errorf("%v", err)
   350  		}
   351  	}
   352  
   353  	fmt.Println(resp.Payload.Route.Path, "created with", resp.Payload.Route.Image)
   354  	return nil
   355  }
   356  
   357  func (a *routesCmd) patchRoute(c *cli.Context, appName, routePath string, r *fnmodels.Route) error {
   358  	if r.Image != "" {
   359  		err := validateImageName(r.Image)
   360  		if err != nil {
   361  			return err
   362  		}
   363  	}
   364  
   365  	_, err := a.client.Routes.PatchAppsAppRoutesRoute(&apiroutes.PatchAppsAppRoutesRouteParams{
   366  		Context: context.Background(),
   367  		App:     appName,
   368  		Route:   routePath,
   369  		Body:    &fnmodels.RouteWrapper{Route: r},
   370  	})
   371  
   372  	if err != nil {
   373  		switch e := err.(type) {
   374  		case *apiroutes.PatchAppsAppRoutesRouteBadRequest:
   375  			return fmt.Errorf("%s", e.Payload.Error.Message)
   376  		case *apiroutes.PatchAppsAppRoutesRouteNotFound:
   377  			return fmt.Errorf("%s", e.Payload.Error.Message)
   378  		case *apiroutes.PatchAppsAppRoutesRouteDefault:
   379  			return fmt.Errorf("%s", e.Payload.Error.Message)
   380  		default:
   381  			return fmt.Errorf("%v", err)
   382  		}
   383  	}
   384  
   385  	return nil
   386  }
   387  
   388  func (a *routesCmd) putRoute(c *cli.Context, appName, routePath string, r *fnmodels.Route) error {
   389  	_, err := a.client.Routes.PutAppsAppRoutesRoute(&apiroutes.PutAppsAppRoutesRouteParams{
   390  		Context: context.Background(),
   391  		App:     appName,
   392  		Route:   routePath,
   393  		Body:    &fnmodels.RouteWrapper{Route: r},
   394  	})
   395  	if err != nil {
   396  		switch e := err.(type) {
   397  		case *apiroutes.PutAppsAppRoutesRouteBadRequest:
   398  			return fmt.Errorf("%s", e.Payload.Error.Message)
   399  		case *apiroutes.PutAppsAppRoutesRouteDefault:
   400  			return fmt.Errorf("%s", e.Payload.Error.Message)
   401  		default:
   402  			return fmt.Errorf("%v", err)
   403  		}
   404  	}
   405  	return nil
   406  }
   407  
   408  func (a *routesCmd) update(c *cli.Context) error {
   409  	appName := c.Args().Get(0)
   410  	route := cleanRoutePath(c.Args().Get(1))
   411  
   412  	rt := &fnmodels.Route{}
   413  
   414  	routeWithFlags(c, rt)
   415  
   416  	err := a.patchRoute(c, appName, route, rt)
   417  	if err != nil {
   418  		return err
   419  	}
   420  
   421  	fmt.Println(appName, route, "updated")
   422  	return nil
   423  }
   424  
   425  func (a *routesCmd) configSet(c *cli.Context) error {
   426  	appName := c.Args().Get(0)
   427  	route := cleanRoutePath(c.Args().Get(1))
   428  	key := c.Args().Get(2)
   429  	value := c.Args().Get(3)
   430  
   431  	patchRoute := fnmodels.Route{
   432  		Config: make(map[string]string),
   433  	}
   434  
   435  	patchRoute.Config[key] = value
   436  
   437  	err := a.patchRoute(c, appName, route, &patchRoute)
   438  	if err != nil {
   439  		return err
   440  	}
   441  
   442  	fmt.Println(appName, route, "updated", key, "with", value)
   443  	return nil
   444  }
   445  
   446  func (a *routesCmd) configUnset(c *cli.Context) error {
   447  	appName := c.Args().Get(0)
   448  	route := cleanRoutePath(c.Args().Get(1))
   449  	key := c.Args().Get(2)
   450  
   451  	patchRoute := fnmodels.Route{
   452  		Config: make(map[string]string),
   453  	}
   454  
   455  	patchRoute.Config[key] = ""
   456  
   457  	err := a.patchRoute(c, appName, route, &patchRoute)
   458  	if err != nil {
   459  		return err
   460  	}
   461  
   462  	fmt.Printf("removed key '%s' from the route '%s%s'", key, appName, key)
   463  	return nil
   464  }
   465  
   466  func (a *routesCmd) inspect(c *cli.Context) error {
   467  	appName := c.Args().Get(0)
   468  	route := cleanRoutePath(c.Args().Get(1))
   469  	prop := c.Args().Get(2)
   470  
   471  	resp, err := a.client.Routes.GetAppsAppRoutesRoute(&apiroutes.GetAppsAppRoutesRouteParams{
   472  		Context: context.Background(),
   473  		App:     appName,
   474  		Route:   route,
   475  	})
   476  
   477  	if err != nil {
   478  		switch e := err.(type) {
   479  		case *apiroutes.GetAppsAppRoutesRouteNotFound:
   480  			return fmt.Errorf("%s", e.Payload.Error.Message)
   481  		case *apiroutes.GetAppsAppRoutesRouteDefault:
   482  			return fmt.Errorf("%s", e.Payload.Error.Message)
   483  		default:
   484  			return fmt.Errorf("%v", err)
   485  		}
   486  	}
   487  
   488  	enc := json.NewEncoder(os.Stdout)
   489  	enc.SetIndent("", "\t")
   490  
   491  	if prop == "" {
   492  		enc.Encode(resp.Payload.Route)
   493  		return nil
   494  	}
   495  
   496  	data, err := json.Marshal(resp.Payload.Route)
   497  	if err != nil {
   498  		return fmt.Errorf("failed to inspect route: %s", err)
   499  	}
   500  	var inspect map[string]interface{}
   501  	err = json.Unmarshal(data, &inspect)
   502  	if err != nil {
   503  		return fmt.Errorf("failed to inspect route: %s", err)
   504  	}
   505  
   506  	jq := jsonq.NewQuery(inspect)
   507  	field, err := jq.Interface(strings.Split(prop, ".")...)
   508  	if err != nil {
   509  		return errors.New("failed to inspect that route's field")
   510  	}
   511  	enc.Encode(field)
   512  
   513  	return nil
   514  }
   515  
   516  func (a *routesCmd) delete(c *cli.Context) error {
   517  	appName := c.Args().Get(0)
   518  	route := cleanRoutePath(c.Args().Get(1))
   519  
   520  	_, err := a.client.Routes.DeleteAppsAppRoutesRoute(&apiroutes.DeleteAppsAppRoutesRouteParams{
   521  		Context: context.Background(),
   522  		App:     appName,
   523  		Route:   route,
   524  	})
   525  	if err != nil {
   526  		switch e := err.(type) {
   527  		case *apiroutes.DeleteAppsAppRoutesRouteNotFound:
   528  			return fmt.Errorf("%s", e.Payload.Error.Message)
   529  		case *apiroutes.DeleteAppsAppRoutesRouteDefault:
   530  			return fmt.Errorf("%s", e.Payload.Error.Message)
   531  		default:
   532  			return fmt.Errorf("%v", err)
   533  		}
   534  	}
   535  
   536  	fmt.Println(appName, route, "deleted")
   537  	return nil
   538  }