github.com/fnproject/cli@v0.0.0-20240508150455-e5d88bd86117/objects/app/apps.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 app 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "os" 24 "text/tabwriter" 25 26 "context" 27 "strings" 28 29 "github.com/fnproject/cli/client" 30 "github.com/fnproject/cli/common" 31 32 fnclient "github.com/fnproject/fn_go/clientv2" 33 apiapps "github.com/fnproject/fn_go/clientv2/apps" 34 "github.com/fnproject/fn_go/modelsv2" 35 "github.com/fnproject/fn_go/provider" 36 "github.com/jmoiron/jsonq" 37 "github.com/urfave/cli" 38 ) 39 40 const ( 41 SHAPE_PARAMETER = "shape" 42 ) 43 44 type appsCmd struct { 45 provider provider.Provider 46 client *fnclient.Fn 47 } 48 49 func printApps(c *cli.Context, apps []*modelsv2.App) error { 50 outputFormat := strings.ToLower(c.String("output")) 51 if outputFormat == "json" { 52 var allApps []interface{} 53 for _, app := range apps { 54 a := struct { 55 Name string `json:"name"` 56 ID string `json:"id"` 57 }{app.Name, 58 app.ID, 59 } 60 allApps = append(allApps, a) 61 } 62 b, err := json.MarshalIndent(allApps, "", " ") 63 if err != nil { 64 return err 65 } 66 fmt.Fprint(os.Stdout, string(b)) 67 } else { 68 w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) 69 fmt.Fprint(w, "NAME", "\t", "ID", "\t", "\n") 70 for _, app := range apps { 71 fmt.Fprint(w, app.Name, "\t", app.ID, "\t", "\n") 72 73 } 74 if err := w.Flush(); err != nil { 75 return err 76 } 77 } 78 return nil 79 } 80 81 func (a *appsCmd) list(c *cli.Context) error { 82 resApps, err := getApps(c, a.client) 83 if err != nil { 84 return err 85 } 86 return printApps(c, resApps) 87 } 88 89 // getApps returns an array of all apps in the given context and client 90 func getApps(c *cli.Context, client *fnclient.Fn) ([]*modelsv2.App, error) { 91 params := &apiapps.ListAppsParams{Context: context.Background()} 92 var resApps []*modelsv2.App 93 for { 94 resp, err := client.Apps.ListApps(params) 95 if err != nil { 96 return nil, err 97 } 98 99 resApps = append(resApps, resp.Payload.Items...) 100 101 n := c.Int64("n") 102 103 howManyMore := n - int64(len(resApps)+len(resp.Payload.Items)) 104 if howManyMore <= 0 || resp.Payload.NextCursor == "" { 105 break 106 } 107 108 params.Cursor = &resp.Payload.NextCursor 109 } 110 111 if len(resApps) == 0 { 112 fmt.Fprint(os.Stderr, "No apps found\n") 113 return nil, nil 114 } 115 return resApps, nil 116 } 117 118 // BashCompleteApps can be called from a BashComplete function 119 // to provide app completion suggestions (Does not check if the 120 // current context already contains an app name as an argument. 121 // This should be checked before calling this) 122 func BashCompleteApps(c *cli.Context) { 123 provider, err := client.CurrentProvider() 124 if err != nil { 125 return 126 } 127 resp, err := getApps(c, provider.APIClientv2()) 128 if err != nil { 129 return 130 } 131 for _, r := range resp { 132 fmt.Println(r.Name) 133 } 134 } 135 136 func appWithFlags(c *cli.Context, app *modelsv2.App) { 137 if c.IsSet("syslog-url") { 138 str := c.String("syslog-url") 139 app.SyslogURL = &str 140 } 141 if len(c.StringSlice("config")) > 0 { 142 app.Config = common.ExtractConfig(c.StringSlice("config")) 143 } 144 if len(c.StringSlice("annotation")) > 0 { 145 app.Annotations = common.ExtractAnnotations(c) 146 } 147 } 148 149 func (a *appsCmd) create(c *cli.Context) error { 150 app := &modelsv2.App{ 151 Name: c.Args().Get(0), 152 } 153 154 appWithFlags(c, app) 155 // If architectures flag is not set then default it to nil 156 if c.IsSet(SHAPE_PARAMETER) { 157 shapeParam := c.String(SHAPE_PARAMETER) 158 159 // Check for architectures parameter passed or set to default 160 if len(shapeParam) == 0 { 161 return errors.New("no shape specified for the application") 162 } 163 164 if _, ok := common.ShapeMap[shapeParam]; !ok { 165 return errors.New("invalid shape specified for the application") 166 } 167 app.Shape = shapeParam 168 } 169 _, err := CreateApp(a.client, app) 170 return err 171 } 172 173 // CreateApp creates a new app using the given client 174 func CreateApp(a *fnclient.Fn, app *modelsv2.App) (*modelsv2.App, error) { 175 resp, err := a.Apps.CreateApp(&apiapps.CreateAppParams{ 176 Context: context.Background(), 177 Body: app, 178 }) 179 180 if err != nil { 181 switch e := err.(type) { 182 case *apiapps.CreateAppBadRequest: 183 err = fmt.Errorf("%v", e.Payload.Message) 184 case *apiapps.CreateAppConflict: 185 err = fmt.Errorf("%v", e.Payload.Message) 186 } 187 return nil, err 188 } 189 fmt.Println("Successfully created app: ", resp.Payload.Name) 190 return resp.Payload, nil 191 } 192 193 func (a *appsCmd) update(c *cli.Context) error { 194 appName := c.Args().First() 195 196 app, err := GetAppByName(a.client, appName) 197 if err != nil { 198 return err 199 } 200 201 appWithFlags(c, app) 202 203 if _, err = PutApp(a.client, app.ID, app); err != nil { 204 return err 205 } 206 207 fmt.Println("app", appName, "updated") 208 return nil 209 } 210 211 func (a *appsCmd) setConfig(c *cli.Context) error { 212 appName := c.Args().Get(0) 213 key := c.Args().Get(1) 214 value := c.Args().Get(2) 215 216 app, err := GetAppByName(a.client, appName) 217 if err != nil { 218 return err 219 } 220 221 app.Config = make(map[string]string) 222 app.Config[key] = value 223 224 if _, err = PutApp(a.client, app.ID, app); err != nil { 225 return fmt.Errorf("Error updating app configuration: %v", err) 226 } 227 228 fmt.Println(appName, "updated", key, "with", value) 229 return nil 230 } 231 232 func (a *appsCmd) getConfig(c *cli.Context) error { 233 appName := c.Args().Get(0) 234 key := c.Args().Get(1) 235 236 app, err := GetAppByName(a.client, appName) 237 if err != nil { 238 return err 239 } 240 241 val, ok := app.Config[key] 242 if !ok { 243 return fmt.Errorf("config key does not exist") 244 } 245 246 fmt.Println(val) 247 248 return nil 249 } 250 251 func (a *appsCmd) listConfig(c *cli.Context) error { 252 appName := c.Args().Get(0) 253 254 app, err := GetAppByName(a.client, appName) 255 if err != nil { 256 return err 257 } 258 259 if len(app.Config) == 0 { 260 fmt.Fprintf(os.Stderr, "No config found for app: %s\n", appName) 261 return nil 262 } 263 264 w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) 265 fmt.Fprint(w, "KEY", "\t", "VALUE", "\n") 266 for key, val := range app.Config { 267 fmt.Fprint(w, key, "\t", val, "\n") 268 } 269 w.Flush() 270 271 return nil 272 } 273 274 func (a *appsCmd) unsetConfig(c *cli.Context) error { 275 appName := c.Args().Get(0) 276 key := c.Args().Get(1) 277 278 app, err := GetAppByName(a.client, appName) 279 if err != nil { 280 return err 281 } 282 _, ok := app.Config[key] 283 if !ok { 284 fmt.Printf("Config key '%s' does not exist. Nothing to do.\n", key) 285 return nil 286 } 287 app.Config[key] = "" 288 289 _, err = PutApp(a.client, app.ID, app) 290 if err != nil { 291 return err 292 } 293 294 fmt.Printf("Removed key '%s' from app '%s' \n", key, appName) 295 return nil 296 } 297 298 func (a *appsCmd) inspect(c *cli.Context) error { 299 if c.Args().Get(0) == "" { 300 return errors.New("Missing app name after the inspect command") 301 } 302 303 appName := c.Args().First() 304 prop := c.Args().Get(1) 305 306 app, err := GetAppByName(a.client, appName) 307 if err != nil { 308 return err 309 } 310 311 enc := json.NewEncoder(os.Stdout) 312 enc.SetIndent("", "\t") 313 314 if prop == "" { 315 enc.Encode(app) 316 return nil 317 } 318 319 // TODO: we really need to marshal it here just to 320 // unmarshal as map[string]interface{}? 321 data, err := json.Marshal(app) 322 if err != nil { 323 return fmt.Errorf("Could not marshal app: %v", err) 324 } 325 326 var inspect map[string]interface{} 327 err = json.Unmarshal(data, &inspect) 328 if err != nil { 329 return fmt.Errorf("Could not unmarshal data: %v", err) 330 } 331 332 jq := jsonq.NewQuery(inspect) 333 field, err := jq.Interface(strings.Split(prop, ".")...) 334 if err != nil { 335 return fmt.Errorf("Failed to inspect field %v", prop) 336 } 337 enc.Encode(field) 338 339 return nil 340 } 341 342 func (a *appsCmd) delete(c *cli.Context) error { 343 appName := c.Args().First() 344 if appName == "" { 345 //return errors.New("App name required to delete") 346 } 347 348 app, err := GetAppByName(a.client, appName) 349 if err != nil { 350 return err 351 } 352 353 //recursive delete of sub-objects 354 if c.Bool("recursive") { 355 fns, triggers, err := common.ListFnsAndTriggersInApp(c, a.client, app) 356 if err != nil { 357 return fmt.Errorf("Failed to get associated objects: %s", err) 358 } 359 360 //Forced deletion 361 var shouldContinue bool 362 if c.Bool("force") { 363 shouldContinue = true 364 } else { 365 shouldContinue = common.UserConfirmedMultiResourceDeletion([]*modelsv2.App{app}, fns, triggers) 366 } 367 368 if shouldContinue { 369 err := common.DeleteTriggers(c, a.client, triggers) 370 if err != nil { 371 return fmt.Errorf("Failed to delete associated objects: %s", err) 372 } 373 err = common.DeleteFunctions(c, a.client, fns) 374 if err != nil { 375 return fmt.Errorf("Failed to delete associated objects: %s", err) 376 } 377 } else { 378 return nil 379 } 380 } 381 382 _, err = a.client.Apps.DeleteApp(&apiapps.DeleteAppParams{ 383 Context: context.Background(), 384 AppID: app.ID, 385 }) 386 387 if err != nil { 388 switch e := err.(type) { 389 case *apiapps.DeleteAppNotFound: 390 return errors.New(e.Payload.Message) 391 } 392 return err 393 } 394 395 fmt.Println("App", appName, "deleted") 396 return nil 397 } 398 399 // PutApp updates the app with the given ID using the content of the provided app 400 func PutApp(a *fnclient.Fn, appID string, app *modelsv2.App) (*modelsv2.App, error) { 401 resp, err := a.Apps.UpdateApp(&apiapps.UpdateAppParams{ 402 Context: context.Background(), 403 AppID: appID, 404 Body: app, 405 }) 406 407 if err != nil { 408 switch e := err.(type) { 409 case *apiapps.UpdateAppBadRequest: 410 err = fmt.Errorf("%s", e.Payload.Message) 411 } 412 return nil, err 413 } 414 415 return resp.Payload, nil 416 } 417 418 // NameNotFoundError error for app not found when looked up by name 419 type NameNotFoundError struct { 420 Name string 421 } 422 423 func (n NameNotFoundError) Error() string { 424 return fmt.Sprintf("app %s not found", n.Name) 425 } 426 427 // GetAppByName looks up an app by name using the given client 428 func GetAppByName(client *fnclient.Fn, appName string) (*modelsv2.App, error) { 429 appsResp, err := client.Apps.ListApps(&apiapps.ListAppsParams{ 430 Context: context.Background(), 431 Name: &appName, 432 }) 433 if err != nil { 434 return nil, err 435 } 436 437 var app *modelsv2.App 438 if len(appsResp.Payload.Items) > 0 { 439 app = appsResp.Payload.Items[0] 440 } else { 441 return nil, NameNotFoundError{appName} 442 } 443 444 return app, nil 445 }