From e63cc04e6834bf4aea294536ad075e50346ca7df Mon Sep 17 00:00:00 2001 From: C Cirello Date: Thu, 1 Dec 2016 17:51:26 +0100 Subject: [PATCH] fn: add header configuration to route calls (#371) --- docs/swagger.yml | 8 ++- fn/README.md | 20 +++++++ fn/funcfile.go | 27 ++++----- fn/publish.go | 14 +++-- fn/routes.go | 147 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 195 insertions(+), 21 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 9ab535b03..4eb488a71 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -6,7 +6,7 @@ swagger: '2.0' info: title: IronFunctions description: The open source serverless platform. - version: "0.1.22" + version: "0.1.23" # the domain of the service host: "127.0.0.1:8080" # array of all schemes that your API supports @@ -326,8 +326,12 @@ definitions: description: Name of Docker image to use in this route. You should include the image tag, which should be a version number, to be more accurate. Can be overridden on a per route basis with route.image. type: string headers: - type: string + type: object description: Map of http headers that will be sent with the response + additionalProperties: + type: array + items: + type: string memory: type: integer format: int64 diff --git a/fn/README.md b/fn/README.md index 1adccdcbd..453a05896 100644 --- a/fn/README.md +++ b/fn/README.md @@ -80,6 +80,9 @@ $ fn routes create otherapp /hello iron/hello # create route $ fn routes delete otherapp hello # delete route /hello deleted +$ fn routes headers set otherapp hello header-name value # add HTTP header to response +otherapp /hello headers updated header-name with value + $ fn version # shows version both of client and server Client version: 0.1.0 Server version: 0.1.21 @@ -126,6 +129,23 @@ 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 +``` + ## Changing target host `fn` is configured by default to talk http://localhost:8080. diff --git a/fn/funcfile.go b/fn/funcfile.go index 87e8ab55f..9b349a9cd 100644 --- a/fn/funcfile.go +++ b/fn/funcfile.go @@ -24,19 +24,20 @@ var ( ) type funcfile struct { - App *string `yaml:"app,omitempty",json:"app,omitempty"` - Name string `yaml:"name,omitempty",json:"name,omitempty"` - Version string `yaml:"version,omitempty",json:"version,omitempty"` - Runtime *string `yaml:"runtime,omitempty",json:"runtime,omitempty"` - Entrypoint *string `yaml:"entrypoint,omitempty",json:"entrypoint,omitempty"` - Route *string `yaml:"route,omitempty",json:"route,omitempty"` - Type *string `yaml:"type,omitempty",json:"type,omitempty"` - Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"` - Format *string `yaml:"format,omitempty",json:"format,omitempty"` - Timeout *time.Duration `yaml:"timeout,omitempty",json:"timeout,omitempty"` - MaxConcurrency *int `yaml:"int,omitempty",json:"int,omitempty"` - Config map[string]string `yaml:"config,omitempty",json:"config,omitempty"` - Build []string `yaml:"build,omitempty",json:"build,omitempty"` + App *string `yaml:"app,omitempty",json:"app,omitempty"` + Name string `yaml:"name,omitempty",json:"name,omitempty"` + Version string `yaml:"version,omitempty",json:"version,omitempty"` + Runtime *string `yaml:"runtime,omitempty",json:"runtime,omitempty"` + Entrypoint *string `yaml:"entrypoint,omitempty",json:"entrypoint,omitempty"` + Route *string `yaml:"route,omitempty",json:"route,omitempty"` + Type *string `yaml:"type,omitempty",json:"type,omitempty"` + Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"` + Format *string `yaml:"format,omitempty",json:"format,omitempty"` + Timeout *time.Duration `yaml:"timeout,omitempty",json:"timeout,omitempty"` + MaxConcurrency *int `yaml:"int,omitempty",json:"int,omitempty"` + Headers map[string][]string `yaml:"headers,omitempty",json:"headers,omitempty"` + Config map[string]string `yaml:"config,omitempty",json:"config,omitempty"` + Build []string `yaml:"build,omitempty",json:"build,omitempty"` } func (ff *funcfile) FullName() string { diff --git a/fn/publish.go b/fn/publish.go index da99587d6..b93976b77 100644 --- a/fn/publish.go +++ b/fn/publish.go @@ -112,11 +112,15 @@ func (p *publishcmd) route(path string, ff *funcfile) error { body := functions.RouteWrapper{ Route: functions.Route{ - Path: *ff.Route, - Image: ff.FullName(), - Memory: *ff.Memory, - Type_: *ff.Type, - Config: expandEnvConfig(ff.Config), + Path: *ff.Route, + Image: ff.FullName(), + AppName: *ff.App, + Memory: *ff.Memory, + Type_: *ff.Type, + Config: expandEnvConfig(ff.Config), + Headers: ff.Headers, + Timeout: int32(ff.Timeout.Seconds()), + MaxConcurrency: int32(*ff.MaxConcurrency), }, } diff --git a/fn/routes.go b/fn/routes.go index 175249dc6..f3af6b57f 100644 --- a/fn/routes.go +++ b/fn/routes.go @@ -71,7 +71,7 @@ func routes() cli.Command { Value: "", }, cli.IntFlag{ - Name: "max-concurrency,m", + Name: "max-concurrency", Usage: "maximum concurrency for hot container", Value: 1, }, @@ -126,6 +126,34 @@ func routes() cli.Command { }, }, }, + + { + 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, + }, + }, + }, }, } } @@ -458,3 +486,120 @@ func (a *routesCmd) configUnset(c *cli.Context) error { fmt.Println(appName, wrapper.Route.Path, "removed", key) return nil } + +func (a *routesCmd) headersList(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") + } + + 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) + wrapper, _, err := a.AppsAppRoutesRouteGet(appName, route) + if err != nil { + return fmt.Errorf("error loading route information: %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") + } + + fmt.Println(wrapper.Route.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) + if err != nil { + return fmt.Errorf("error loading 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.AppsAppRoutesRoutePut(appName, route, *wrapper); err != nil { + return fmt.Errorf("error updating route configuration: %v", err) + } + + fmt.Println(wrapper.Route.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) + if err != nil { + return fmt.Errorf("error loading app: %v", err) + } + + 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.AppsAppRoutesRoutePut(appName, route, *wrapper); err != nil { + return fmt.Errorf("error updating route configuration: %v", err) + } + + fmt.Println(wrapper.Route.AppName, wrapper.Route.Path, "removed header", key) + return nil +}