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