github.com/fnproject/cli@v0.0.0-20240508150455-e5d88bd86117/objects/trigger/triggers.go (about) 1 /* 2 * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package trigger 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "os" 25 "strings" 26 "text/tabwriter" 27 28 "github.com/fnproject/fn_go/clientv2/fns" 29 30 "github.com/jmoiron/jsonq" 31 32 "github.com/fnproject/cli/client" 33 "github.com/fnproject/cli/common" 34 "github.com/fnproject/cli/objects/app" 35 "github.com/fnproject/cli/objects/fn" 36 fnclient "github.com/fnproject/fn_go/clientv2" 37 apiTriggers "github.com/fnproject/fn_go/clientv2/triggers" 38 models "github.com/fnproject/fn_go/modelsv2" 39 "github.com/fnproject/fn_go/provider" 40 "github.com/urfave/cli" 41 ) 42 43 type triggersCmd struct { 44 provider provider.Provider 45 client *fnclient.Fn 46 } 47 48 // TriggerFlags used to create/update triggers 49 var TriggerFlags = []cli.Flag{ 50 cli.StringFlag{ 51 Name: "source,s", 52 Usage: "trigger source", 53 }, 54 cli.StringFlag{ 55 Name: "type, t", 56 Usage: "Todo", 57 }, 58 cli.StringSliceFlag{ 59 Name: "annotation", 60 Usage: "fn annotation (can be specified multiple times)", 61 }, 62 } 63 64 func (t *triggersCmd) create(c *cli.Context) error { 65 appName := c.Args().Get(0) 66 fnName := c.Args().Get(1) 67 triggerName := c.Args().Get(2) 68 69 app, err := app.GetAppByName(t.client, appName) 70 if err != nil { 71 return err 72 } 73 74 fn, err := fn.GetFnByName(t.client, app.ID, fnName) 75 if err != nil { 76 return err 77 } 78 79 trigger := &models.Trigger{ 80 AppID: app.ID, 81 FnID: fn.ID, 82 } 83 84 trigger.Name = triggerName 85 86 if triggerType := c.String("type"); triggerType != "" { 87 trigger.Type = triggerType 88 } 89 90 if triggerSource := c.String("source"); triggerSource != "" { 91 trigger.Source = validateTriggerSource(triggerSource) 92 } 93 94 WithFlags(c, trigger) 95 96 if trigger.Name == "" { 97 return errors.New("triggerName path is missing") 98 } 99 100 return CreateTrigger(t.client, trigger) 101 } 102 103 func validateTriggerSource(ts string) string { 104 if !strings.HasPrefix(ts, "/") { 105 ts = "/" + ts 106 } 107 return ts 108 } 109 110 // CreateTrigger request 111 func CreateTrigger(client *fnclient.Fn, trigger *models.Trigger) error { 112 resp, err := client.Triggers.CreateTrigger(&apiTriggers.CreateTriggerParams{ 113 Context: context.Background(), 114 Body: trigger, 115 }) 116 117 if err != nil { 118 switch e := err.(type) { 119 case *apiTriggers.CreateTriggerBadRequest: 120 fmt.Println(e) 121 return fmt.Errorf("%s", e.Payload.Message) 122 case *apiTriggers.CreateTriggerConflict: 123 return fmt.Errorf("%s", e.Payload.Message) 124 default: 125 return err 126 } 127 } 128 129 fmt.Println("Successfully created trigger:", resp.Payload.Name) 130 endpoint := resp.Payload.Annotations["fnproject.io/trigger/httpEndpoint"] 131 fmt.Println("Trigger Endpoint:", endpoint) 132 133 return nil 134 } 135 136 func (t *triggersCmd) list(c *cli.Context) error { 137 resTriggers, err := getTriggers(c, t.client) 138 if err != nil { 139 return err 140 } 141 fnName := c.Args().Get(1) 142 w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) 143 if len(fnName) != 0 { 144 145 fmt.Fprint(w, "NAME", "\t", "ID", "\t", "TYPE", "\t", "SOURCE", "\t", "ENDPOINT", "\n") 146 for _, trigger := range resTriggers { 147 endpoint := trigger.Annotations["fnproject.io/trigger/httpEndpoint"] 148 fmt.Fprint(w, trigger.Name, "\t", trigger.ID, "\t", trigger.Type, "\t", trigger.Source, "\t", endpoint, "\n") 149 } 150 } else { 151 fmt.Fprint(w, "FUNCTION", "\t", "NAME", "\t", "ID", "\t", "TYPE", "\t", "SOURCE", "\t", "ENDPOINT", "\n") 152 for _, trigger := range resTriggers { 153 endpoint := trigger.Annotations["fnproject.io/trigger/httpEndpoint"] 154 155 resp, err := t.client.Fns.GetFn(&fns.GetFnParams{ 156 FnID: trigger.FnID, 157 Context: context.Background(), 158 }) 159 if err != nil { 160 return err 161 } 162 fnName = resp.Payload.Name 163 fmt.Fprint(w, fnName, "\t", trigger.Name, "\t", trigger.ID, "\t", trigger.Type, "\t", trigger.Source, "\t", endpoint, "\n") 164 } 165 } 166 w.Flush() 167 return nil 168 } 169 170 func getTriggers(c *cli.Context, client *fnclient.Fn) ([]*models.Trigger, error) { 171 appName := c.Args().Get(0) 172 fnName := c.Args().Get(1) 173 var params *apiTriggers.ListTriggersParams 174 175 app, err := app.GetAppByName(client, appName) 176 if err != nil { 177 return nil, err 178 } 179 180 if len(fnName) == 0 { 181 params = &apiTriggers.ListTriggersParams{ 182 Context: context.Background(), 183 AppID: &app.ID, 184 } 185 186 } else { 187 188 fn, err := fn.GetFnByName(client, app.ID, fnName) 189 if err != nil { 190 return nil, err 191 } 192 params = &apiTriggers.ListTriggersParams{ 193 Context: context.Background(), 194 AppID: &app.ID, 195 FnID: &fn.ID, 196 } 197 } 198 199 var resTriggers []*models.Trigger 200 for { 201 202 resp, err := client.Triggers.ListTriggers(params) 203 if err != nil { 204 return nil, err 205 } 206 n := c.Int64("n") 207 208 resTriggers = append(resTriggers, resp.Payload.Items...) 209 howManyMore := n - int64(len(resTriggers)+len(resp.Payload.Items)) 210 if howManyMore <= 0 || resp.Payload.NextCursor == "" { 211 break 212 } 213 214 params.Cursor = &resp.Payload.NextCursor 215 } 216 217 if len(resTriggers) == 0 { 218 if len(fnName) == 0 { 219 return nil, fmt.Errorf("no triggers found for app: %s", appName) 220 } 221 return nil, fmt.Errorf("no triggers found for function: %s", fnName) 222 } 223 return resTriggers, nil 224 } 225 226 // BashCompleteTriggers can be called from a BashComplete function 227 // to provide function completion suggestions (Assumes the 228 // current context already contains an app name and a function name 229 // as the first 2 arguments. This should be confirmed before calling this) 230 func BashCompleteTriggers(c *cli.Context) { 231 provider, err := client.CurrentProvider() 232 if err != nil { 233 return 234 } 235 resp, err := getTriggers(c, provider.APIClientv2()) 236 if err != nil { 237 return 238 } 239 for _, t := range resp { 240 fmt.Println(t.Name) 241 } 242 } 243 244 func (t *triggersCmd) update(c *cli.Context) error { 245 appName := c.Args().Get(0) 246 fnName := c.Args().Get(1) 247 triggerName := c.Args().Get(2) 248 249 trigger, err := GetTrigger(t.client, appName, fnName, triggerName) 250 if err != nil { 251 return err 252 } 253 254 WithFlags(c, trigger) 255 256 err = PutTrigger(t.client, trigger) 257 if err != nil { 258 return err 259 } 260 261 fmt.Println(appName, fnName, triggerName, "updated") 262 return nil 263 } 264 265 // PutTrigger updates the provided trigger with new values 266 func PutTrigger(t *fnclient.Fn, trigger *models.Trigger) error { 267 _, err := t.Triggers.UpdateTrigger(&apiTriggers.UpdateTriggerParams{ 268 Context: context.Background(), 269 TriggerID: trigger.ID, 270 Body: trigger, 271 }) 272 273 if err != nil { 274 switch e := err.(type) { 275 case *apiTriggers.UpdateTriggerBadRequest: 276 return fmt.Errorf("%s", e.Payload.Message) 277 default: 278 return err 279 } 280 } 281 282 return nil 283 } 284 285 func (t *triggersCmd) inspect(c *cli.Context) error { 286 appName := c.Args().Get(0) 287 fnName := c.Args().Get(1) 288 triggerName := c.Args().Get(2) 289 prop := c.Args().Get(3) 290 291 trigger, err := GetTrigger(t.client, appName, fnName, triggerName) 292 if err != nil { 293 return err 294 } 295 296 if c.Bool("endpoint") { 297 endpoint, ok := trigger.Annotations["fnproject.io/trigger/httpEndpoint"].(string) 298 if !ok { 299 return errors.New("missing or invalid http endpoint on trigger") 300 } 301 fmt.Println(endpoint) 302 return nil 303 } 304 305 enc := json.NewEncoder(os.Stdout) 306 enc.SetIndent("", "\t") 307 308 if prop == "" { 309 enc.Encode(trigger) 310 return nil 311 } 312 313 data, err := json.Marshal(trigger) 314 if err != nil { 315 return fmt.Errorf("failed to inspect %s: %s", triggerName, err) 316 } 317 318 var inspect map[string]interface{} 319 err = json.Unmarshal(data, &inspect) 320 if err != nil { 321 return fmt.Errorf("failed to inspect %s: %s", triggerName, err) 322 } 323 324 jq := jsonq.NewQuery(inspect) 325 field, err := jq.Interface(strings.Split(prop, ".")...) 326 if err != nil { 327 return errors.New("failed to inspect %s field names") 328 } 329 enc.Encode(field) 330 331 return nil 332 } 333 334 func (t *triggersCmd) delete(c *cli.Context) error { 335 appName := c.Args().Get(0) 336 fnName := c.Args().Get(1) 337 triggerName := c.Args().Get(2) 338 339 trigger, err := GetTrigger(t.client, appName, fnName, triggerName) 340 if err != nil { 341 return err 342 } 343 344 params := apiTriggers.NewDeleteTriggerParams() 345 params.TriggerID = trigger.ID 346 347 _, err = t.client.Triggers.DeleteTrigger(params) 348 if err != nil { 349 return err 350 } 351 352 fmt.Println(appName, fnName, triggerName, "deleted") 353 return nil 354 } 355 356 // GetTrigger looks up a trigger using the provided client by app, function and trigger name 357 func GetTrigger(client *fnclient.Fn, appName, fnName, triggerName string) (*models.Trigger, error) { 358 app, err := app.GetAppByName(client, appName) 359 if err != nil { 360 return nil, err 361 } 362 363 fn, err := fn.GetFnByName(client, app.ID, fnName) 364 if err != nil { 365 return nil, err 366 } 367 368 trigger, err := GetTriggerByName(client, app.ID, fn.ID, triggerName) 369 if err != nil { 370 return nil, err 371 } 372 373 return trigger, nil 374 } 375 376 // GetTriggerByAppFnAndTriggerNames looks up a trigger using app, fn and trigger names 377 func GetTriggerByAppFnAndTriggerNames(appName, fnName, triggerName string) (*models.Trigger, error) { 378 provider, err := client.CurrentProvider() 379 if err != nil { 380 return nil, err 381 } 382 client := provider.APIClientv2() 383 return GetTrigger(client, appName, fnName, triggerName) 384 } 385 386 // GetTriggerByName looks up a trigger using the provided client by app and function ID and trigger name 387 func GetTriggerByName(client *fnclient.Fn, appID string, fnID string, triggerName string) (*models.Trigger, error) { 388 triggerList, err := client.Triggers.ListTriggers(&apiTriggers.ListTriggersParams{ 389 Context: context.Background(), 390 AppID: &appID, 391 FnID: &fnID, 392 Name: &triggerName, 393 }) 394 395 if err != nil { 396 return nil, err 397 } 398 if len(triggerList.Payload.Items) == 0 { 399 return nil, NameNotFoundError{triggerName} 400 } 401 402 return triggerList.Payload.Items[0], nil 403 } 404 405 // WithFlags returns a trigger with the specified flags 406 func WithFlags(c *cli.Context, t *models.Trigger) { 407 if len(c.StringSlice("annotation")) > 0 { 408 t.Annotations = common.ExtractAnnotations(c) 409 } 410 } 411 412 // NameNotFoundError error for app not found when looked up by name 413 type NameNotFoundError struct { 414 Name string 415 } 416 417 func (n NameNotFoundError) Error() string { 418 return fmt.Sprintf("trigger %s not found", n.Name) 419 }