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 }