From bf048c9222f78a73874a1bf4841a71291dfbd7c7 Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Tue, 3 Jan 2017 18:31:26 -0200 Subject: [PATCH] Include missing update commands (#461) * include missing update commands * merge all update commands into one single command * added route inspect command * fn routes inspect improvement * update fn docs --- fn/README.md | 37 ++--- fn/glide.lock | 6 +- fn/glide.yaml | 3 +- fn/routes.go | 370 +++++++++++++++++++++----------------------------- 4 files changed, 170 insertions(+), 246 deletions(-) diff --git a/fn/README.md b/fn/README.md index 617518072..e9e03f019 100644 --- a/fn/README.md +++ b/fn/README.md @@ -114,37 +114,22 @@ Thus a more complete example of route creation will look like: fn routes create --memory 256 --type async --config DB_URL=http://example.org/ otherapp /hello iron/hello ``` -`--memory` is number of usable MiB for this function. If during the execution it -exceeds this maximum threshold, it will halt and return an error in the logs. +You can also update existent routes configurations using the command `fn routes update` -`--type` is the type of the function. Either `sync`, in which the client waits -until the request is successfully completed, or `async`, in which the clients -dispatches a new request, gets a task ID back and closes the HTTP connection. +For example: -`--config` is a map of values passed to the route runtime in the form of -environment variables. - -Repeated calls to `fn route create` will trigger an update of the given -route, thus you will be able to change any of these attributes later in time -if necessary. - -## Route headers - -You can configure a route's HTTP response to return specific headers. - -A header configuration workflow example: ```sh -$ fn routes headers set otherapp hello header-name value -otherapp /hello headers updated header-name with value - -$ fn routes headers view otherapp hello -otherapp /hello headers: -header-name: [value] - -$ fn routes headers unset otherapp hello header-name -otherapp /hello removed header header-name +fn routes update --memory 64 --type sync --image iron/hello ``` +To know exactly what configurations you can update just use the command + +``` +fn routes update --help +``` + +To understand how each configuration affect your function checkout the [Definitions](/docs/definitions.md#Routes) document. + ## Changing target host `fn` is configured by default to talk http://localhost:8080. diff --git a/fn/glide.lock b/fn/glide.lock index da7fe19e2..cab0f9592 100644 --- a/fn/glide.lock +++ b/fn/glide.lock @@ -1,5 +1,5 @@ -hash: f3c0e4634313b824f30782a3431b6fb8ad2feaf382c765e6f6930bfd50f53750 -updated: 2016-12-01T21:51:57.931016569+01:00 +hash: 9356255bb45ddb833b045e985b2dbf0721791b431849a4f36a50b0897e888c3c +updated: 2016-12-29T19:40:42.322381915-02:00 imports: - name: github.com/aws/aws-sdk-go version: 90dec2183a5f5458ee79cbaf4b8e9ab910bc81a6 @@ -90,6 +90,8 @@ imports: - lambda - name: github.com/jmespath/go-jmespath version: 3433f3ea46d9f8019119e7dd41274e112a2359a9 +- name: github.com/jmoiron/jsonq + version: e874b168d07ecc7808bc950a17998a8aa3141d82 - name: github.com/juju/errgo version: 08cceb5d0b5331634b9826762a8fd53b29b86ad8 subpackages: diff --git a/fn/glide.yaml b/fn/glide.yaml index 921169b77..a4c115c41 100644 --- a/fn/glide.yaml +++ b/fn/glide.yaml @@ -14,9 +14,10 @@ import: - bump - storage - package: github.com/iron-io/functions_go - version: 429df8920abd7c47dfcd6777dba278d6122ab93d + version: f38f2174656467ec3ed404b7294e9ee172573e43 - package: github.com/iron-io/lambda subpackages: - lambda - package: github.com/urfave/cli - package: gopkg.in/yaml.v2 +- package: github.com/jmoiron/jsonq \ No newline at end of file diff --git a/fn/routes.go b/fn/routes.go index 21fd97dca..dbc2997b4 100644 --- a/fn/routes.go +++ b/fn/routes.go @@ -14,6 +14,7 @@ import ( "time" functions "github.com/iron-io/functions_go" + "github.com/jmoiron/jsonq" "github.com/urfave/cli" ) @@ -81,6 +82,52 @@ func routes() cli.Command { }, }, }, + { + Name: "update", + Aliases: []string{"u"}, + Usage: "update a route in an `app`", + ArgsUsage: "`app` /path", + Action: r.update, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "image,i", + Usage: "image name", + }, + cli.Int64Flag{ + Name: "memory,m", + Usage: "memory in MiB", + Value: 128, + }, + cli.StringFlag{ + Name: "type,t", + Usage: "route type - sync or async", + Value: "sync", + }, + cli.StringSliceFlag{ + Name: "config,c", + Usage: "route configuration", + }, + cli.StringSliceFlag{ + Name: "headers", + Usage: "route response headers", + }, + cli.StringFlag{ + Name: "format,f", + Usage: "hot container IO format - json or http", + Value: "", + }, + cli.IntFlag{ + Name: "max-concurrency", + Usage: "maximum concurrency for hot container", + Value: 1, + }, + cli.DurationFlag{ + Name: "timeout", + Usage: "route timeout", + Value: 30 * time.Second, + }, + }, + }, { Name: "delete", Aliases: []string{"d"}, @@ -89,69 +136,11 @@ func routes() cli.Command { Action: r.delete, }, { - Name: "config", - Usage: "operate a route configuration set", - Subcommands: []cli.Command{ - { - Name: "view", - Aliases: []string{"v"}, - Usage: "view all configuration keys for this route", - ArgsUsage: "`app` /path", - Action: r.configList, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "shell,s", - Usage: "output in shell format", - }, - cli.BoolFlag{ - Name: "json,j", - Usage: "output in JSON format", - }, - }, - }, - { - Name: "set", - Aliases: []string{"s"}, - Usage: "store a configuration key for this route", - ArgsUsage: "`app` /path ", - Action: r.configSet, - }, - { - Name: "unset", - Aliases: []string{"u"}, - Usage: "remove a configuration key for this route", - ArgsUsage: "`app` /path ", - Action: r.configUnset, - }, - }, - }, - - { - Name: "headers", - Usage: "operate a route's header configuration", - Subcommands: []cli.Command{ - { - Name: "view", - Aliases: []string{"v"}, - Usage: "view all route's headers", - ArgsUsage: "`app` /path", - Action: r.headersList, - }, - { - Name: "set", - Aliases: []string{"s"}, - Usage: "add header to a router", - ArgsUsage: "`app` /path ", - Action: r.headersSet, - }, - { - Name: "unset", - Aliases: []string{"u"}, - Usage: "remove a configuration key for this route", - ArgsUsage: "`app` /path ", - Action: r.headersUnset, - }, - }, + Name: "inspect", + Aliases: []string{"i"}, + Usage: "retrieve one or all routes properties", + ArgsUsage: "`app` /path [property.[key]]", + Action: r.inspect, }, }, } @@ -376,6 +365,49 @@ func (a *routesCmd) delete(c *cli.Context) error { return nil } +func (a *routesCmd) update(c *cli.Context) error { + if c.Args().Get(0) == "" { + return errors.New("error: routes creation takes at least one argument: an app name") + } + + if err := resetBasePath(a.Configuration); err != nil { + return fmt.Errorf("error setting endpoint: %v", err) + } + + appName := c.Args().Get(0) + route := c.Args().Get(1) + + if route == "" { + return errors.New("error: route path is missing") + } + + headers := map[string][]string{} + for _, header := range c.StringSlice("headers") { + parts := strings.Split(header, "=") + headers[parts[0]] = strings.Split(parts[1], ";") + } + + patchedRoute := &functions.Route{ + Path: route, + Image: c.String("image"), + Memory: c.Int64("memory"), + Type_: c.String("type"), + Config: extractEnvConfig(c.StringSlice("config")), + Headers: headers, + Format: c.String("format"), + MaxConcurrency: int32(c.Int64("max-concurrency")), + Timeout: int32(c.Int64("timeout")), + } + + err := a.patchRoute(appName, route, patchedRoute) + if err != nil { + return err + } + + fmt.Println(appName, route, "updated") + return nil +} + func (a *routesCmd) configList(c *cli.Context) error { if c.Args().Get(0) == "" || c.Args().Get(1) == "" { return errors.New("error: route configuration description takes two arguments: an app name and a route") @@ -421,21 +453,8 @@ func (a *routesCmd) configList(c *cli.Context) error { return nil } -func (a *routesCmd) configSet(c *cli.Context) error { - if c.Args().Get(0) == "" || c.Args().Get(1) == "" || c.Args().Get(2) == "" { - return errors.New("error: route configuration setting takes four arguments: an app name, a route, a key and a value") - } - - if err := resetBasePath(a.Configuration); err != nil { - return fmt.Errorf("error setting endpoint: %v", err) - } - - appName := c.Args().Get(0) - route := c.Args().Get(1) - key := c.Args().Get(2) - value := c.Args().Get(3) - - wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) +func (a *routesCmd) patchRoute(appName, routePath string, r *functions.Route) error { + wrapper, _, err := a.AppsAppRoutesRouteGet(appName, routePath) if err != nil { return fmt.Errorf("error loading route: %v", err) } @@ -444,69 +463,57 @@ func (a *routesCmd) configSet(c *cli.Context) error { return errors.New(msg) } - config := wrapper.Route.Config - - if config == nil { - config = make(map[string]string) + wrapper.Route.Path = "" + if r != nil { + if r.Config != nil { + for k, v := range r.Config { + if v == "" { + delete(r.Config, k) + continue + } + wrapper.Route.Config[k] = v + } + } + if r.Headers != nil { + for k, v := range r.Headers { + if v[0] == "" { + delete(r.Headers, k) + continue + } + wrapper.Route.Headers[k] = v + } + } + if r.Image != "" { + wrapper.Route.Image = r.Image + } + if r.Format != "" { + wrapper.Route.Format = r.Format + } + if r.MaxConcurrency > 0 { + wrapper.Route.MaxConcurrency = r.MaxConcurrency + } + if r.Memory > 0 { + wrapper.Route.Memory = r.Memory + } + if r.Timeout > 0 { + wrapper.Route.Timeout = r.Timeout + } } - config[key] = value - wrapper.Route.Config = config - - if _, _, err := a.AppsAppRoutesRoutePatch(appName, route, *wrapper); err != nil { - return fmt.Errorf("error updating route configuration: %v", err) - } - - fmt.Println(appName, wrapper.Route.Path, "updated", key, "with", value) - return nil -} - -func (a *routesCmd) configUnset(c *cli.Context) error { - if c.Args().Get(0) == "" || c.Args().Get(1) == "" || c.Args().Get(2) == "" { - return errors.New("error: route configuration setting takes four arguments: an app name, a route and a key") - } - - if err := resetBasePath(a.Configuration); err != nil { - return fmt.Errorf("error setting endpoint: %v", err) - } - - appName := c.Args().Get(0) - route := c.Args().Get(1) - key := c.Args().Get(2) - - wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) - if err != nil { - return fmt.Errorf("error loading app: %v", err) + if wrapper, _, err = a.AppsAppRoutesRoutePatch(appName, routePath, *wrapper); err != nil { + return fmt.Errorf("error updating route: %v", err) } if msg := wrapper.Error_.Message; msg != "" { return errors.New(msg) } - config := wrapper.Route.Config - - if config == nil { - config = make(map[string]string) - } - - if _, ok := config[key]; !ok { - return fmt.Errorf("configuration key %s not found", key) - } - - delete(config, key) - wrapper.Route.Config = config - - if _, _, err := a.AppsAppRoutesRoutePatch(appName, route, *wrapper); err != nil { - return fmt.Errorf("error updating route configuration: %v", err) - } - - fmt.Println(appName, wrapper.Route.Path, "removed", key) return nil } -func (a *routesCmd) headersList(c *cli.Context) error { +func (a *routesCmd) inspect(c *cli.Context) error { if c.Args().Get(0) == "" || c.Args().Get(1) == "" { - return errors.New("error: route configuration description takes two arguments: an app name and a route") + return errors.New("error: routes listing takes three arguments: an app name and a path") } if err := resetBasePath(a.Configuration); err != nil { @@ -515,108 +522,37 @@ func (a *routesCmd) headersList(c *cli.Context) error { appName := c.Args().Get(0) route := c.Args().Get(1) - wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) + prop := c.Args().Get(2) + + wrapper, resp, err := a.AppsAppRoutesRouteGet(appName, route) if err != nil { - return fmt.Errorf("error loading route information: %v", err) + return fmt.Errorf("error retrieving route: %v", err) } if msg := wrapper.Error_.Message; msg != "" { return errors.New(msg) } - headers := wrapper.Route.Headers - if len(headers) == 0 { - return errors.New("this route has no headers") + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", "\t") + + if prop == "" { + enc.Encode(wrapper.Route) + return nil } - fmt.Println(appName, wrapper.Route.Path, "headers:") - w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0) - for k, v := range headers { - fmt.Fprint(w, k, ":\t", v, "\n") - } - w.Flush() - return nil -} - -func (a *routesCmd) headersSet(c *cli.Context) error { - if c.Args().Get(0) == "" || c.Args().Get(1) == "" || c.Args().Get(2) == "" { - return errors.New("error: route configuration setting takes four arguments: an app name, a route, a key and a value") - } - - if err := resetBasePath(a.Configuration); err != nil { - return fmt.Errorf("error setting endpoint: %v", err) - } - - appName := c.Args().Get(0) - route := c.Args().Get(1) - key := c.Args().Get(2) - value := c.Args().Get(3) - - wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) + var inspect struct{ Route map[string]interface{} } + err = json.Unmarshal(resp.Payload, &inspect) if err != nil { - return fmt.Errorf("error loading route: %v", err) + return fmt.Errorf("error inspect route: %v", err) } - if msg := wrapper.Error_.Message; msg != "" { - return errors.New(msg) - } - - headers := wrapper.Route.Headers - - if headers == nil { - headers = make(map[string][]string) - } - - headers[key] = append(headers[key], value) - wrapper.Route.Headers = headers - - if _, _, err := a.AppsAppRoutesRoutePatch(appName, route, *wrapper); err != nil { - return fmt.Errorf("error updating route configuration: %v", err) - } - - fmt.Println(appName, wrapper.Route.Path, "headers updated", key, "with", value) - return nil -} - -func (a *routesCmd) headersUnset(c *cli.Context) error { - if c.Args().Get(0) == "" || c.Args().Get(1) == "" || c.Args().Get(2) == "" { - return errors.New("error: route configuration setting takes four arguments: an app name, a route and a key") - } - - if err := resetBasePath(a.Configuration); err != nil { - return fmt.Errorf("error setting endpoint: %v", err) - } - - appName := c.Args().Get(0) - route := c.Args().Get(1) - key := c.Args().Get(2) - - wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) + jq := jsonq.NewQuery(inspect.Route) + field, err := jq.Interface(strings.Split(prop, ".")...) if err != nil { - return fmt.Errorf("error loading app: %v", err) + return errors.New("failed to inspect the property") } + enc.Encode(field) - if msg := wrapper.Error_.Message; msg != "" { - return errors.New(msg) - } - - headers := wrapper.Route.Headers - - if headers == nil { - headers = make(map[string][]string) - } - - if _, ok := headers[key]; !ok { - return fmt.Errorf("configuration key %s not found", key) - } - - delete(headers, key) - wrapper.Route.Headers = headers - - if _, _, err := a.AppsAppRoutesRoutePatch(appName, route, *wrapper); err != nil { - return fmt.Errorf("error updating route configuration: %v", err) - } - - fmt.Println(appName, wrapper.Route.Path, "removed header", key) return nil }