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 }