github.com/IBM-Cloud/bluemix-go@v0.0.0-20240314082800-4e02a69b84b2/api/mccp/mccpv2/apps.go (about) 1 package mccpv2 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "time" 8 9 "github.com/IBM-Cloud/bluemix-go/bmxerror" 10 "github.com/IBM-Cloud/bluemix-go/client" 11 "github.com/IBM-Cloud/bluemix-go/helpers" 12 "github.com/IBM-Cloud/bluemix-go/rest" 13 "github.com/IBM-Cloud/bluemix-go/trace" 14 ) 15 16 //AppState ... 17 type AppState struct { 18 PackageState string 19 InstanceState string 20 } 21 22 const ( 23 //ErrCodeAppDoesnotExist ... 24 ErrCodeAppDoesnotExist = "AppADoesnotExist" 25 26 //AppRunningState ... 27 AppRunningState = "RUNNING" 28 29 //AppStartedState ... 30 AppStartedState = "STARTED" 31 32 //AppStagedState ... 33 AppStagedState = "STAGED" 34 35 //AppPendingState ... 36 AppPendingState = "PENDING" 37 38 //AppStoppedState ... 39 AppStoppedState = "STOPPED" 40 41 //AppFailedState ... 42 AppFailedState = "FAILED" 43 44 //AppUnKnownState ... 45 AppUnKnownState = "UNKNOWN" 46 47 //DefaultRetryDelayForStatusCheck ... 48 DefaultRetryDelayForStatusCheck = 10 * time.Second 49 ) 50 51 //AppRequest ... 52 type AppRequest struct { 53 Name *string `json:"name,omitempty"` 54 Memory int `json:"memory,omitempty"` 55 Instances int `json:"instances,omitempty"` 56 DiskQuota int `json:"disk_quota,omitempty"` 57 SpaceGUID *string `json:"space_guid,omitempty"` 58 StackGUID *string `json:"stack_guid,omitempty"` 59 State *string `json:"state,omitempty"` 60 DetectedStartCommand *string `json:"detected_start_command,omitempty"` 61 Command *string `json:"command,omitempty"` 62 BuildPack *string `json:"buildpack,omitempty"` 63 HealthCheckType *string `json:"health_check_type,omitempty"` 64 HealthCheckTimeout int `json:"health_check_timeout,omitempty"` 65 HealthCheckHTTPEndpoint *string `json:"health_check_http_endpoint,omitempty"` 66 Diego bool `json:"diego,omitempty"` 67 EnableSSH bool `json:"enable_ssh,omitempty"` 68 DockerImage *string `json:"docker_image,omitempty"` 69 StagingFailedReason *string `json:"staging_failed_reason,omitempty"` 70 StagingFailedDescription *string `json:"staging_failed_description,omitempty"` 71 Ports []int `json:"ports,omitempty"` 72 DockerCredentialsJSON *map[string]interface{} `json:"docker_credentials_json,omitempty"` 73 EnvironmentJSON *map[string]interface{} `json:"environment_json,omitempty"` 74 } 75 76 //AppEntity ... 77 type AppEntity struct { 78 Name string `json:"name"` 79 SpaceGUID string `json:"space_guid"` 80 StackGUID string `json:"stack_guid"` 81 State string `json:"state"` 82 PackageState string `json:"package_state"` 83 Memory int `json:"memory"` 84 Instances int `json:"instances"` 85 DiskQuota int `json:"disk_quota"` 86 Version string `json:"version"` 87 BuildPack *string `json:"buildpack"` 88 Command *string `json:"command"` 89 Console bool `json:"console"` 90 Debug *string `json:"debug"` 91 StagingTaskID string `json:"staging_task_id"` 92 HealthCheckType string `json:"health_check_type"` 93 HealthCheckTimeout *int `json:"health_check_timeout"` 94 HealthCheckHTTPEndpoint string `json:"health_check_http_endpoint"` 95 StagingFailedReason string `json:"staging_failed_reason"` 96 StagingFailedDescription string `json:"staging_failed_description"` 97 Diego bool `json:"diego"` 98 DockerImage *string `json:"docker_image"` 99 EnableSSH bool `json:"enable_ssh"` 100 Ports []int `json:"ports"` 101 DockerCredentialsJSON map[string]interface{} `json:"docker_credentials_json"` 102 EnvironmentJSON map[string]interface{} `json:"environment_json"` 103 } 104 105 //AppResource ... 106 type AppResource struct { 107 Resource 108 Entity AppEntity 109 } 110 111 //AppFields ... 112 type AppFields struct { 113 Metadata Metadata 114 Entity AppEntity 115 } 116 117 //UploadBitsEntity ... 118 type UploadBitsEntity struct { 119 GUID string `json:"guid"` 120 Status string `json:"status"` 121 } 122 123 //UploadBitFields ... 124 type UploadBitFields struct { 125 Metadata Metadata 126 Entity UploadBitsEntity 127 } 128 129 //AppSummaryFields ... 130 type AppSummaryFields struct { 131 GUID string `json:"guid"` 132 Name string `json:"name"` 133 State string `json:"state"` 134 PackageState string `json:"package_state"` 135 RunningInstances int `json:"running_instances"` 136 } 137 138 //AppStats ... 139 type AppStats struct { 140 State string `json:"state"` 141 } 142 143 //ToFields .. 144 func (resource AppResource) ToFields() App { 145 entity := resource.Entity 146 147 return App{ 148 GUID: resource.Metadata.GUID, 149 Name: entity.Name, 150 SpaceGUID: entity.SpaceGUID, 151 StackGUID: entity.StackGUID, 152 State: entity.State, 153 PackageState: entity.PackageState, 154 Memory: entity.Memory, 155 Instances: entity.Instances, 156 DiskQuota: entity.DiskQuota, 157 Version: entity.Version, 158 BuildPack: entity.BuildPack, 159 Command: entity.Command, 160 Console: entity.Console, 161 Debug: entity.Debug, 162 StagingTaskID: entity.StagingTaskID, 163 HealthCheckType: entity.HealthCheckType, 164 HealthCheckTimeout: entity.HealthCheckTimeout, 165 HealthCheckHTTPEndpoint: entity.HealthCheckHTTPEndpoint, 166 Diego: entity.Diego, 167 DockerImage: entity.DockerImage, 168 EnableSSH: entity.EnableSSH, 169 Ports: entity.Ports, 170 DockerCredentialsJSON: entity.DockerCredentialsJSON, 171 EnvironmentJSON: entity.EnvironmentJSON, 172 } 173 } 174 175 //App model 176 type App struct { 177 Name string 178 SpaceGUID string 179 GUID string 180 StackGUID string 181 State string 182 PackageState string 183 Memory int 184 Instances int 185 DiskQuota int 186 Version string 187 BuildPack *string 188 Command *string 189 Console bool 190 Debug *string 191 StagingTaskID string 192 HealthCheckType string 193 HealthCheckTimeout *int 194 HealthCheckHTTPEndpoint string 195 Diego bool 196 DockerImage *string 197 EnableSSH bool 198 Ports []int 199 DockerCredentialsJSON map[string]interface{} 200 EnvironmentJSON map[string]interface{} 201 } 202 203 //Apps ... 204 type Apps interface { 205 Create(appPayload AppRequest, opts ...bool) (*AppFields, error) 206 List() ([]App, error) 207 Get(appGUID string) (*AppFields, error) 208 Update(appGUID string, appPayload AppRequest, opts ...bool) (*AppFields, error) 209 Delete(appGUID string, opts ...bool) error 210 FindByName(spaceGUID, name string) (*App, error) 211 Start(appGUID string, timeout time.Duration) (*AppState, error) 212 Upload(path string, name string, opts ...bool) (*UploadBitFields, error) 213 Summary(appGUID string) (*AppSummaryFields, error) 214 Stat(appGUID string) (map[string]AppStats, error) 215 WaitForAppStatus(waitForThisState, appGUID string, timeout time.Duration) (string, error) 216 WaitForInstanceStatus(waitForThisState, appGUID string, timeout time.Duration) (string, error) 217 Instances(appGUID string) (map[string]AppStats, error) 218 Restage(appGUID string, timeout time.Duration) (*AppState, error) 219 WaitForStatus(appGUID string, maxWaitTime time.Duration) (*AppState, error) 220 221 //Routes related 222 BindRoute(appGUID, routeGUID string) (*AppFields, error) 223 ListRoutes(appGUID string) ([]Route, error) 224 UnBindRoute(appGUID, routeGUID string) error 225 226 //Service bindings 227 ListServiceBindings(appGUID string) ([]ServiceBinding, error) 228 DeleteServiceBindings(appGUID string, bindingGUIDs ...string) error 229 } 230 231 type app struct { 232 client *client.Client 233 } 234 235 func newAppAPI(c *client.Client) Apps { 236 return &app{ 237 client: c, 238 } 239 } 240 241 func (r *app) FindByName(spaceGUID string, name string) (*App, error) { 242 rawURL := fmt.Sprintf("/v2/spaces/%s/apps", spaceGUID) 243 req := rest.GetRequest(rawURL).Query("q", "name:"+name) 244 httpReq, err := req.Build() 245 if err != nil { 246 return nil, err 247 } 248 path := httpReq.URL.String() 249 apps, err := r.listAppWithPath(path) 250 if err != nil { 251 return nil, err 252 } 253 if len(apps) == 0 { 254 return nil, bmxerror.New(ErrCodeAppDoesnotExist, 255 fmt.Sprintf("Given app: %q doesn't exist in given space: %q", name, spaceGUID)) 256 257 } 258 return &apps[0], nil 259 } 260 261 // opts is list of boolean parametes 262 // opts[0] - async - Will run the create request in a background job. Recommended: 'true'. Default to 'true'. 263 func (r *app) Create(appPayload AppRequest, opts ...bool) (*AppFields, error) { 264 async := true 265 if len(opts) > 0 { 266 async = opts[0] 267 } 268 rawURL := fmt.Sprintf("/v2/apps?async=%t", async) 269 appFields := AppFields{} 270 _, err := r.client.Post(rawURL, appPayload, &appFields) 271 if err != nil { 272 return nil, err 273 } 274 return &appFields, nil 275 } 276 277 func (r *app) BindRoute(appGUID, routeGUID string) (*AppFields, error) { 278 rawURL := fmt.Sprintf("/v2/apps/%s/routes/%s", appGUID, routeGUID) 279 appFields := AppFields{} 280 _, err := r.client.Put(rawURL, nil, &appFields) 281 if err != nil { 282 return nil, err 283 } 284 return &appFields, nil 285 } 286 287 func (r *app) ListRoutes(appGUID string) ([]Route, error) { 288 rawURL := fmt.Sprintf("/v2/apps/%s/routes", appGUID) 289 req := rest.GetRequest(rawURL) 290 httpReq, err := req.Build() 291 if err != nil { 292 return nil, err 293 } 294 path := httpReq.URL.String() 295 route, err := listRouteWithPath(r.client, path) 296 if err != nil { 297 return nil, err 298 } 299 return route, nil 300 } 301 302 func (r *app) UnBindRoute(appGUID, routeGUID string) error { 303 rawURL := fmt.Sprintf("/v2/apps/%s/routes/%s", appGUID, routeGUID) 304 _, err := r.client.Delete(rawURL) 305 return err 306 } 307 308 func (r *app) DeleteServiceBindings(appGUID string, sbGUIDs ...string) error { 309 for _, g := range sbGUIDs { 310 rawURL := fmt.Sprintf("/v2/apps/%s/service_bindings/%s", appGUID, g) 311 _, err := r.client.Delete(rawURL) 312 return err 313 } 314 return nil 315 } 316 317 func (r *app) listAppWithPath(path string) ([]App, error) { 318 var apps []App 319 _, err := r.client.GetPaginated(path, NewCCPaginatedResources(AppResource{}), func(resource interface{}) bool { 320 if appResource, ok := resource.(AppResource); ok { 321 apps = append(apps, appResource.ToFields()) 322 return true 323 } 324 return false 325 }) 326 return apps, err 327 } 328 329 // opts is list of boolean parametes 330 // opts[0] - async - If true, a new asynchronous job is submitted to persist the bits and the job id is included in the response. 331 // The client will need to poll the job's status until persistence is completed successfully. 332 // If false, the request will block until the bits are persisted synchronously. Defaults to 'false'. 333 334 func (r *app) Upload(appGUID string, zipPath string, opts ...bool) (*UploadBitFields, error) { 335 async := false 336 if len(opts) > 0 { 337 async = opts[0] 338 } 339 req := rest.PutRequest(r.client.URL("/v2/apps/"+appGUID+"/bits")).Query("async", strconv.FormatBool(async)) 340 file, err := os.Open(zipPath) 341 if err != nil { 342 return nil, err 343 } 344 defer file.Close() 345 346 f := rest.File{ 347 Name: file.Name(), 348 Content: file, 349 } 350 req.File("application", f) 351 req.Field("resources", "[]") 352 uploadBitResponse := &UploadBitFields{} 353 _, err = r.client.SendRequest(req, uploadBitResponse) 354 return uploadBitResponse, err 355 } 356 357 func (r *app) Start(appGUID string, maxWaitTime time.Duration) (*AppState, error) { 358 payload := AppRequest{ 359 State: helpers.String(AppStartedState), 360 } 361 rawURL := fmt.Sprintf("/v2/apps/%s", appGUID) 362 appFields := AppFields{} 363 _, err := r.client.Put(rawURL, payload, &appFields) 364 if err != nil { 365 return nil, err 366 } 367 appState := &AppState{ 368 PackageState: AppPendingState, 369 InstanceState: AppUnKnownState, 370 } 371 if maxWaitTime == 0 { 372 appState.PackageState = appFields.Entity.PackageState 373 appState.InstanceState = appFields.Entity.State 374 return appState, nil 375 } 376 return r.WaitForStatus(appGUID, maxWaitTime) 377 378 } 379 380 func (r *app) Get(appGUID string) (*AppFields, error) { 381 rawURL := fmt.Sprintf("/v2/apps/%s", appGUID) 382 appFields := AppFields{} 383 _, err := r.client.Get(rawURL, &appFields, nil) 384 if err != nil { 385 return nil, err 386 } 387 return &appFields, nil 388 } 389 390 func (r *app) Summary(appGUID string) (*AppSummaryFields, error) { 391 rawURL := fmt.Sprintf("/v2/apps/%s/summary", appGUID) 392 appFields := AppSummaryFields{} 393 _, err := r.client.Get(rawURL, &appFields, nil) 394 if err != nil { 395 return nil, err 396 } 397 return &appFields, nil 398 } 399 400 func (r *app) Stat(appGUID string) (map[string]AppStats, error) { 401 rawURL := fmt.Sprintf("/v2/apps/%s/stats", appGUID) 402 appStats := map[string]AppStats{} 403 _, err := r.client.Get(rawURL, &appStats, nil) 404 if err != nil { 405 return nil, err 406 } 407 return appStats, nil 408 } 409 410 func (r *app) Instances(appGUID string) (map[string]AppStats, error) { 411 412 rawURL := fmt.Sprintf("/v2/apps/%s/instances", appGUID) 413 appInstances := map[string]AppStats{} 414 _, err := r.client.Get(rawURL, &appInstances, nil) 415 if err != nil { 416 return nil, err 417 } 418 return appInstances, nil 419 } 420 421 func (r *app) List() ([]App, error) { 422 rawURL := "v2/apps" 423 req := rest.GetRequest(rawURL) 424 httpReq, err := req.Build() 425 if err != nil { 426 return nil, err 427 } 428 path := httpReq.URL.String() 429 apps, err := r.listAppWithPath(path) 430 if err != nil { 431 return nil, err 432 } 433 return apps, nil 434 435 } 436 437 // opts is list of boolean parametes 438 // opts[0] - async - Will run the update request in a background job. Recommended: 'true'. Default to 'true'. 439 func (r *app) Update(appGUID string, appPayload AppRequest, opts ...bool) (*AppFields, error) { 440 async := true 441 if len(opts) > 0 { 442 async = opts[0] 443 } 444 rawURL := fmt.Sprintf("/v2/apps/%s?async=%t", appGUID, async) 445 appFields := AppFields{} 446 _, err := r.client.Put(rawURL, appPayload, &appFields) 447 if err != nil { 448 return nil, err 449 } 450 return &appFields, nil 451 } 452 453 // opts is list of boolean parametes 454 // opts[0] - async - Will run the delete request in a background job. Recommended: 'true'. Default to 'true'. 455 // opts[1] - recursive - Will delete service bindings, and routes associated with the app. Default to 'false'. 456 func (r *app) Delete(appGUID string, opts ...bool) error { 457 async := true 458 recursive := false 459 if len(opts) > 0 { 460 async = opts[0] 461 } 462 if len(opts) > 1 { 463 recursive = opts[1] 464 } 465 rawURL := fmt.Sprintf("/v2/apps/%s?async=%t&recursive=%t", appGUID, async, recursive) 466 _, err := r.client.Delete(rawURL) 467 return err 468 } 469 470 func (r *app) Restage(appGUID string, maxWaitTime time.Duration) (*AppState, error) { 471 rawURL := fmt.Sprintf("/v2/apps/%s/restage", appGUID) 472 appFields := AppFields{} 473 _, err := r.client.Post(rawURL, nil, &appFields) 474 if err != nil { 475 return nil, err 476 } 477 appState := &AppState{ 478 PackageState: AppPendingState, 479 InstanceState: AppUnKnownState, 480 } 481 if maxWaitTime == 0 { 482 appState.PackageState = appFields.Entity.PackageState 483 appState.InstanceState = appFields.Entity.State 484 return appState, nil 485 } 486 return r.WaitForStatus(appGUID, maxWaitTime) 487 488 } 489 490 func (r *app) WaitForAppStatus(waitForThisState, appGUID string, maxWaitTime time.Duration) (string, error) { 491 timeout := time.After(maxWaitTime) 492 tick := time.NewTicker(DefaultRetryDelayForStatusCheck) 493 defer tick.Stop() 494 status := AppPendingState 495 for { 496 select { 497 case <-timeout: 498 trace.Logger.Printf("Timed out while checking the app status for %q. Waited for %q for the state to be %q", appGUID, maxWaitTime, waitForThisState) 499 return status, nil 500 case <-tick.C: 501 appFields, err := r.Get(appGUID) 502 if err != nil { 503 return "", err 504 } 505 status = appFields.Entity.PackageState 506 trace.Logger.Println("apps.Entity.PackageState ===>>> ", status) 507 if status == waitForThisState || status == AppFailedState { 508 return status, nil 509 } 510 } 511 } 512 } 513 514 func (r *app) WaitForInstanceStatus(waitForThisState, appGUID string, maxWaitTime time.Duration) (string, error) { 515 timeout := time.After(maxWaitTime) 516 tick := time.NewTicker(DefaultRetryDelayForStatusCheck) 517 defer tick.Stop() 518 status := AppStartedState 519 for { 520 select { 521 case <-timeout: 522 trace.Logger.Printf("Timed out while checking the app status for %q. Waited for %q for the state to be %q", appGUID, maxWaitTime, waitForThisState) 523 return status, nil 524 case <-tick.C: 525 appStat, err := r.Stat(appGUID) 526 if err != nil { 527 return status, err 528 } 529 stateCount := 0 530 for k, v := range appStat { 531 fmt.Printf("Instance[%s] State is %s", k, v) 532 if v.State == waitForThisState { 533 stateCount++ 534 } 535 } 536 if stateCount == len(appStat) { 537 return waitForThisState, nil 538 } 539 540 } 541 } 542 543 } 544 545 func (r *app) WaitForStatus(appGUID string, maxWaitTime time.Duration) (*AppState, error) { 546 appState := &AppState{ 547 PackageState: AppPendingState, 548 InstanceState: AppUnKnownState, 549 } 550 status, err := r.WaitForAppStatus(AppStagedState, appGUID, maxWaitTime/2) 551 appState.PackageState = status 552 if err != nil || status == AppFailedState { 553 return appState, err 554 } 555 status, err = r.WaitForInstanceStatus(AppRunningState, appGUID, maxWaitTime/2) 556 appState.InstanceState = status 557 return appState, err 558 } 559 560 //TODO pull the wait logic in a auxiliary function which can be used by all 561 562 func (r *app) ListServiceBindings(appGUID string) ([]ServiceBinding, error) { 563 rawURL := fmt.Sprintf("/v2/apps/%s/service_bindings", appGUID) 564 req := rest.GetRequest(rawURL) 565 httpReq, err := req.Build() 566 if err != nil { 567 return nil, err 568 } 569 path := httpReq.URL.String() 570 sb, err := listServiceBindingWithPath(r.client, path) 571 if err != nil { 572 return nil, err 573 } 574 return sb, nil 575 }