github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/actor/v7action/application.go (about) 1 package v7action 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "code.cloudfoundry.org/cli/actor/actionerror" 9 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant" 12 "code.cloudfoundry.org/cli/resources" 13 "code.cloudfoundry.org/cli/util/batcher" 14 "code.cloudfoundry.org/cli/util/unique" 15 ) 16 17 func (actor Actor) DeleteApplicationByNameAndSpace(name, spaceGUID string, deleteRoutes bool) (Warnings, error) { 18 var allWarnings Warnings 19 var jobQueue []ccv3.JobURL 20 21 app, getAppWarnings, err := actor.GetApplicationByNameAndSpace(name, spaceGUID) 22 allWarnings = append(allWarnings, getAppWarnings...) 23 if err != nil { 24 return allWarnings, err 25 } 26 27 var routes []resources.Route 28 if deleteRoutes { 29 var getRoutesWarnings Warnings 30 routes, getRoutesWarnings, err = actor.GetApplicationRoutes(app.GUID) 31 allWarnings = append(allWarnings, getRoutesWarnings...) 32 if err != nil { 33 return allWarnings, err 34 } 35 36 for _, route := range routes { 37 if len(route.Destinations) > 1 { 38 for _, destination := range route.Destinations { 39 guid := destination.App.GUID 40 if guid != app.GUID { 41 return allWarnings, actionerror.RouteBoundToMultipleAppsError{AppName: app.Name, RouteURL: route.URL} 42 } 43 } 44 } 45 } 46 } 47 48 appDeleteJobURL, deleteAppWarnings, err := actor.CloudControllerClient.DeleteApplication(app.GUID) 49 allWarnings = append(allWarnings, deleteAppWarnings...) 50 if err != nil { 51 return allWarnings, err 52 } 53 54 pollWarnings, err := actor.CloudControllerClient.PollJob(appDeleteJobURL) 55 allWarnings = append(allWarnings, pollWarnings...) 56 if err != nil { 57 return allWarnings, err 58 } 59 60 if deleteRoutes { 61 for _, route := range routes { 62 jobURL, deleteRouteWarnings, err := actor.CloudControllerClient.DeleteRoute(route.GUID) 63 allWarnings = append(allWarnings, deleteRouteWarnings...) 64 if err != nil { 65 if _, ok := err.(ccerror.ResourceNotFoundError); ok { 66 continue 67 } 68 return allWarnings, err 69 } 70 71 jobQueue = append(jobQueue, jobURL) 72 } 73 } 74 75 for _, job := range jobQueue { 76 pollWarnings, err := actor.CloudControllerClient.PollJob(job) 77 allWarnings = append(allWarnings, pollWarnings...) 78 if err != nil { 79 return allWarnings, err 80 } 81 } 82 83 return allWarnings, err 84 } 85 86 func (actor Actor) GetApplicationsByGUIDs(appGUIDs []string) ([]resources.Application, Warnings, error) { 87 uniqueAppGUIDs := unique.StringSlice(appGUIDs) 88 89 var apps []resources.Application 90 warnings, err := batcher.RequestByGUID(appGUIDs, func(guids []string) (ccv3.Warnings, error) { 91 batch, warnings, err := actor.CloudControllerClient.GetApplications( 92 ccv3.Query{Key: ccv3.GUIDFilter, Values: guids}, 93 ) 94 apps = append(apps, batch...) 95 return warnings, err 96 }) 97 98 if err != nil { 99 return nil, Warnings(warnings), err 100 } 101 102 if len(apps) < len(uniqueAppGUIDs) { 103 return nil, Warnings(warnings), actionerror.ApplicationsNotFoundError{} 104 } 105 106 return apps, Warnings(warnings), nil 107 } 108 109 func (actor Actor) GetApplicationsByNamesAndSpace(appNames []string, spaceGUID string) ([]resources.Application, Warnings, error) { 110 uniqueAppNames := unique.StringSlice(appNames) 111 112 apps, warnings, err := actor.CloudControllerClient.GetApplications( 113 ccv3.Query{Key: ccv3.NameFilter, Values: appNames}, 114 ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}}, 115 ) 116 117 if err != nil { 118 return nil, Warnings(warnings), err 119 } 120 121 if len(apps) < len(uniqueAppNames) { 122 return nil, Warnings(warnings), actionerror.ApplicationsNotFoundError{} 123 } 124 125 return apps, Warnings(warnings), nil 126 } 127 128 // GetApplicationByNameAndSpace returns the application with the given 129 // name in the given space. 130 func (actor Actor) GetApplicationByNameAndSpace(appName string, spaceGUID string) (resources.Application, Warnings, error) { 131 apps, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{appName}, spaceGUID) 132 133 if err != nil { 134 if _, ok := err.(actionerror.ApplicationsNotFoundError); ok { 135 return resources.Application{}, warnings, actionerror.ApplicationNotFoundError{Name: appName} 136 } 137 return resources.Application{}, warnings, err 138 } 139 140 return apps[0], warnings, nil 141 } 142 143 // GetApplicationsBySpace returns all applications in a space. 144 func (actor Actor) GetApplicationsBySpace(spaceGUID string) ([]resources.Application, Warnings, error) { 145 apps, warnings, err := actor.CloudControllerClient.GetApplications( 146 ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}}, 147 ) 148 149 if err != nil { 150 return []resources.Application{}, Warnings(warnings), err 151 } 152 153 return apps, Warnings(warnings), nil 154 } 155 156 // CreateApplicationInSpace creates and returns the application with the given 157 // name in the given space. 158 func (actor Actor) CreateApplicationInSpace(app resources.Application, spaceGUID string) (resources.Application, Warnings, error) { 159 createdApp, warnings, err := actor.CloudControllerClient.CreateApplication( 160 resources.Application{ 161 LifecycleType: app.LifecycleType, 162 LifecycleBuildpacks: app.LifecycleBuildpacks, 163 StackName: app.StackName, 164 Name: app.Name, 165 SpaceGUID: spaceGUID, 166 }) 167 168 if err != nil { 169 return resources.Application{}, Warnings(warnings), err 170 } 171 172 return createdApp, Warnings(warnings), nil 173 } 174 175 // SetApplicationProcessHealthCheckTypeByNameAndSpace sets the health check 176 // information of the provided processType for an application with the given 177 // name and space GUID. 178 func (actor Actor) SetApplicationProcessHealthCheckTypeByNameAndSpace( 179 appName string, 180 spaceGUID string, 181 healthCheckType constant.HealthCheckType, 182 httpEndpoint string, 183 processType string, 184 invocationTimeout int64, 185 ) (resources.Application, Warnings, error) { 186 187 app, getWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID) 188 if err != nil { 189 return resources.Application{}, getWarnings, err 190 } 191 192 setWarnings, err := actor.UpdateProcessByTypeAndApplication( 193 processType, 194 app.GUID, 195 resources.Process{ 196 HealthCheckType: healthCheckType, 197 HealthCheckEndpoint: httpEndpoint, 198 HealthCheckInvocationTimeout: invocationTimeout, 199 }) 200 return app, append(getWarnings, setWarnings...), err 201 } 202 203 // StopApplication stops an application. 204 func (actor Actor) StopApplication(appGUID string) (Warnings, error) { 205 _, warnings, err := actor.CloudControllerClient.UpdateApplicationStop(appGUID) 206 207 return Warnings(warnings), err 208 } 209 210 // StartApplication starts an application. 211 func (actor Actor) StartApplication(appGUID string) (Warnings, error) { 212 _, warnings, err := actor.CloudControllerClient.UpdateApplicationStart(appGUID) 213 return Warnings(warnings), err 214 } 215 216 // RestartApplication restarts an application and waits for it to start. 217 func (actor Actor) RestartApplication(appGUID string, noWait bool) (Warnings, error) { 218 // var allWarnings Warnings 219 _, warnings, err := actor.CloudControllerClient.UpdateApplicationRestart(appGUID) 220 return Warnings(warnings), err 221 } 222 223 func (actor Actor) GetUnstagedNewestPackageGUID(appGUID string) (string, Warnings, error) { 224 var err error 225 var allWarnings Warnings 226 packages, warnings, err := actor.CloudControllerClient.GetPackages( 227 ccv3.Query{Key: ccv3.AppGUIDFilter, Values: []string{appGUID}}, 228 ccv3.Query{Key: ccv3.OrderBy, Values: []string{ccv3.CreatedAtDescendingOrder}}, 229 ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}}) 230 allWarnings = append(allWarnings, warnings...) 231 if err != nil { 232 return "", allWarnings, err 233 } 234 if len(packages) == 0 { 235 return "", allWarnings, nil 236 } 237 238 newestPackage := packages[0] 239 240 droplets, warnings, err := actor.CloudControllerClient.GetPackageDroplets( 241 newestPackage.GUID, 242 ccv3.Query{Key: ccv3.StatesFilter, Values: []string{"STAGED"}}, 243 ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}}, 244 ) 245 allWarnings = append(allWarnings, warnings...) 246 if err != nil { 247 return "", allWarnings, err 248 } 249 250 if len(droplets) == 0 { 251 return newestPackage.GUID, allWarnings, nil 252 } 253 254 return "", allWarnings, nil 255 } 256 257 // PollStart polls an application's processes until some are started. If noWait is false, 258 // it waits for at least one instance of all processes to be running. If noWait is true, 259 // it only waits for an instance of the web process to be running. 260 func (actor Actor) PollStart(app resources.Application, noWait bool, handleInstanceDetails func(string)) (Warnings, error) { 261 var allWarnings Warnings 262 processes, warnings, err := actor.CloudControllerClient.GetApplicationProcesses(app.GUID) 263 allWarnings = append(allWarnings, warnings...) 264 if err != nil { 265 return allWarnings, err 266 } 267 268 var filteredProcesses []resources.Process 269 if noWait { 270 for _, process := range processes { 271 if process.Type == constant.ProcessTypeWeb { 272 filteredProcesses = append(filteredProcesses, process) 273 } 274 } 275 } else { 276 filteredProcesses = processes 277 } 278 279 timer := actor.Clock.NewTimer(time.Millisecond) 280 defer timer.Stop() 281 timeout := actor.Clock.After(actor.Config.StartupTimeout()) 282 283 for { 284 select { 285 case <-timeout: 286 return allWarnings, actionerror.StartupTimeoutError{Name: app.Name} 287 case <-timer.C(): 288 stopPolling, warnings, err := actor.PollProcesses(filteredProcesses, handleInstanceDetails) 289 allWarnings = append(allWarnings, warnings...) 290 if stopPolling || err != nil { 291 return allWarnings, err 292 } 293 294 timer.Reset(actor.Config.PollingInterval()) 295 } 296 } 297 } 298 299 // PollStartForRolling polls a deploying application's processes until some are started. It does the same thing as PollStart, except it accounts for rolling deployments and whether 300 // they have failed or been canceled during polling. 301 func (actor Actor) PollStartForRolling(app resources.Application, deploymentGUID string, noWait bool, handleInstanceDetails func(string)) (Warnings, error) { 302 var ( 303 deployment resources.Deployment 304 processes []resources.Process 305 allWarnings Warnings 306 ) 307 308 timer := actor.Clock.NewTimer(time.Millisecond) 309 defer timer.Stop() 310 timeout := actor.Clock.After(actor.Config.StartupTimeout()) 311 312 for { 313 select { 314 case <-timeout: 315 warnings, err := actor.CancelDeployment(deploymentGUID) 316 allWarnings = append(allWarnings, warnings...) 317 if err != nil { 318 return allWarnings, err 319 } 320 return allWarnings, actionerror.StartupTimeoutError{Name: app.Name} 321 case <-timer.C(): 322 if !isDeployed(deployment) { 323 ccDeployment, warnings, err := actor.getDeployment(deploymentGUID) 324 allWarnings = append(allWarnings, warnings...) 325 if err != nil { 326 return allWarnings, err 327 } 328 deployment = ccDeployment 329 processes, warnings, err = actor.getProcesses(deployment, app.GUID, noWait) 330 allWarnings = append(allWarnings, warnings...) 331 if err != nil { 332 return allWarnings, err 333 } 334 } 335 336 if noWait || isDeployed(deployment) { 337 stopPolling, warnings, err := actor.PollProcesses(processes, handleInstanceDetails) 338 allWarnings = append(allWarnings, warnings...) 339 if stopPolling || err != nil { 340 return allWarnings, err 341 } 342 } 343 344 timer.Reset(actor.Config.PollingInterval()) 345 } 346 } 347 } 348 349 func isDeployed(d resources.Deployment) bool { 350 return d.StatusValue == constant.DeploymentStatusValueFinalized && d.StatusReason == constant.DeploymentStatusReasonDeployed 351 } 352 353 // PollProcesses - return true if there's no need to keep polling 354 func (actor Actor) PollProcesses(processes []resources.Process, handleInstanceDetails func(string)) (bool, Warnings, error) { 355 numProcesses := len(processes) 356 numStableProcesses := 0 357 var allWarnings Warnings 358 for _, process := range processes { 359 ccInstances, ccWarnings, err := actor.CloudControllerClient.GetProcessInstances(process.GUID) 360 instances := ProcessInstances(ccInstances) 361 allWarnings = append(allWarnings, ccWarnings...) 362 if err != nil { 363 return true, allWarnings, err 364 } 365 366 handleInstanceDetails(formatInstanceDetails(instances)) 367 368 if instances.Empty() || instances.AnyRunning() { 369 numStableProcesses += 1 370 continue 371 } 372 373 if instances.AllCrashed() { 374 return true, allWarnings, actionerror.AllInstancesCrashedError{} 375 } 376 377 //precondition: !instances.Empty() && no instances are running 378 // do not increment numStableProcesses 379 return false, allWarnings, nil 380 } 381 return numStableProcesses == numProcesses, allWarnings, nil 382 } 383 384 // UpdateApplication updates the buildpacks on an application 385 func (actor Actor) UpdateApplication(app resources.Application) (resources.Application, Warnings, error) { 386 ccApp := resources.Application{ 387 GUID: app.GUID, 388 StackName: app.StackName, 389 LifecycleType: app.LifecycleType, 390 LifecycleBuildpacks: app.LifecycleBuildpacks, 391 Metadata: app.Metadata, 392 Name: app.Name, 393 } 394 395 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccApp) 396 if err != nil { 397 return resources.Application{}, Warnings(warnings), err 398 } 399 400 return updatedApp, Warnings(warnings), nil 401 } 402 403 // UpdateApplicationName updates the name of an application 404 func (actor Actor) UpdateApplicationName(newAppName string, appGUID string) (resources.Application, Warnings, error) { 405 406 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplicationName(newAppName, appGUID) 407 if err != nil { 408 return resources.Application{}, Warnings(warnings), err 409 } 410 411 return updatedApp, Warnings(warnings), nil 412 } 413 414 func (actor Actor) getDeployment(deploymentGUID string) (resources.Deployment, Warnings, error) { 415 deployment, warnings, err := actor.CloudControllerClient.GetDeployment(deploymentGUID) 416 if err != nil { 417 return deployment, Warnings(warnings), err 418 } 419 420 if deployment.StatusValue == constant.DeploymentStatusValueFinalized { 421 switch deployment.StatusReason { 422 case constant.DeploymentStatusReasonCanceled: 423 return deployment, Warnings(warnings), errors.New("Deployment has been canceled") 424 case constant.DeploymentStatusReasonSuperseded: 425 return deployment, Warnings(warnings), errors.New("Deployment has been superseded") 426 } 427 } 428 429 return deployment, Warnings(warnings), err 430 } 431 432 func (actor Actor) getProcesses(deployment resources.Deployment, appGUID string, noWait bool) ([]resources.Process, Warnings, error) { 433 if noWait { 434 // these are only web processes for now so we can just use these 435 return deployment.NewProcesses, nil, nil 436 } 437 438 // if the deployment is deployed we know web are all running and PollProcesses will see those as stable 439 // so just getting all processes is equivalent to just getting non-web ones and polling those 440 if isDeployed(deployment) { 441 processes, warnings, err := actor.CloudControllerClient.GetApplicationProcesses(appGUID) 442 if err != nil { 443 return processes, Warnings(warnings), err 444 } 445 return processes, Warnings(warnings), nil 446 } 447 448 return nil, nil, nil 449 } 450 451 func (actor Actor) RenameApplicationByNameAndSpaceGUID(appName, newAppName, spaceGUID string) (resources.Application, Warnings, error) { 452 allWarnings := Warnings{} 453 application, warnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID) 454 allWarnings = append(allWarnings, warnings...) 455 if err != nil { 456 return resources.Application{}, allWarnings, err 457 } 458 appGUID := application.GUID 459 application, warnings, err = actor.UpdateApplicationName(newAppName, appGUID) 460 allWarnings = append(allWarnings, warnings...) 461 if err != nil { 462 return resources.Application{}, allWarnings, err 463 } 464 465 return application, allWarnings, nil 466 } 467 468 func formatInstanceDetails(instances ProcessInstances) string { 469 for _, instance := range instances { 470 if instance.Details != "" { 471 return fmt.Sprintf("Error starting instances: '%s'", instance.Details) 472 } 473 } 474 return "Instances starting..." 475 }