github.com/swisscom/cloudfoundry-cli@v7.1.0+incompatible/actor/v2action/application.go (about) 1 package v2action 2 3 import ( 4 "fmt" 5 "time" 6 7 "code.cloudfoundry.org/cli/actor/actionerror" 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/constant" 11 ) 12 13 type ApplicationStateChange string 14 15 const ( 16 ApplicationStateStopping ApplicationStateChange = "stopping" 17 ApplicationStateStaging ApplicationStateChange = "staging" 18 ApplicationStateStarting ApplicationStateChange = "starting" 19 ) 20 21 // Application represents an application. 22 type Application ccv2.Application 23 24 // CalculatedBuildpack returns the buildpack that will be used. 25 func (application Application) CalculatedBuildpack() string { 26 if application.Buildpack.IsSet { 27 return application.Buildpack.Value 28 } 29 30 return application.DetectedBuildpack.Value 31 } 32 33 // CalculatedCommand returns the command that will be used. 34 func (application Application) CalculatedCommand() string { 35 if application.Command.IsSet { 36 return application.Command.Value 37 } 38 39 return application.DetectedStartCommand.Value 40 } 41 42 // CalculatedHealthCheckEndpoint returns the health check endpoint. 43 // If the health check type is not http it will return the empty string. 44 func (application Application) CalculatedHealthCheckEndpoint() string { 45 if application.HealthCheckType == "http" { 46 return application.HealthCheckHTTPEndpoint 47 } 48 49 return "" 50 } 51 52 // StagingCompleted returns true if the application has been staged. 53 func (application Application) StagingCompleted() bool { 54 return application.PackageState == constant.ApplicationPackageStaged 55 } 56 57 // StagingFailed returns true if staging the application failed. 58 func (application Application) StagingFailed() bool { 59 return application.PackageState == constant.ApplicationPackageFailed 60 } 61 62 // StagingFailedMessage returns the verbose description of the failure or 63 // the reason if the verbose description is empty. 64 func (application Application) StagingFailedMessage() string { 65 if application.StagingFailedDescription != "" { 66 return application.StagingFailedDescription 67 } 68 69 return application.StagingFailedReason 70 } 71 72 // StagingFailedNoAppDetected returns true when the staging failed due to a 73 // NoAppDetectedError. 74 func (application Application) StagingFailedNoAppDetected() bool { 75 return application.StagingFailedReason == "NoAppDetectedError" 76 } 77 78 // Started returns true when the application is started. 79 func (application Application) Started() bool { 80 return application.State == constant.ApplicationStarted 81 } 82 83 // Stopped returns true when the application is stopped. 84 func (application Application) Stopped() bool { 85 return application.State == constant.ApplicationStopped 86 } 87 88 func (application Application) String() string { 89 return fmt.Sprintf( 90 "App Name: '%s', Buildpack (%t, '%s'), Command: (%t, '%s'), Detected Buildpack: (%t, '%s'), Detected Start Command: (%t, '%s'), Disk Quota: '%d', Docker Image: '%s', Health Check HTTP Endpoint: '%s', Health Check Timeout: '%d', Health Check Type: '%s', Instances: (%t, '%d'), Memory: '%d', Space GUID: '%s', Stack GUID: '%s', State: '%s'", 91 application.Name, 92 application.Buildpack.IsSet, 93 application.Buildpack.Value, 94 application.Command.IsSet, 95 application.Command.Value, 96 application.DetectedBuildpack.IsSet, 97 application.DetectedBuildpack.Value, 98 application.DetectedStartCommand.IsSet, 99 application.DetectedStartCommand.Value, 100 application.DiskQuota.Value, 101 application.DockerImage, 102 application.HealthCheckHTTPEndpoint, 103 application.HealthCheckTimeout, 104 application.HealthCheckType, 105 application.Instances.IsSet, 106 application.Instances.Value, 107 application.Memory.Value, 108 application.SpaceGUID, 109 application.StackGUID, 110 application.State, 111 ) 112 } 113 114 // CreateApplication creates an application. 115 func (actor Actor) CreateApplication(application Application) (Application, Warnings, error) { 116 app, warnings, err := actor.CloudControllerClient.CreateApplication(ccv2.Application(application)) 117 return Application(app), Warnings(warnings), err 118 } 119 120 // GetApplication returns the application. 121 func (actor Actor) GetApplication(guid string) (Application, Warnings, error) { 122 app, warnings, err := actor.CloudControllerClient.GetApplication(guid) 123 124 if _, ok := err.(ccerror.ResourceNotFoundError); ok { 125 return Application{}, Warnings(warnings), actionerror.ApplicationNotFoundError{GUID: guid} 126 } 127 128 return Application(app), Warnings(warnings), err 129 } 130 131 // GetApplicationByNameAndSpace returns an application with matching name in 132 // the space. 133 func (actor Actor) GetApplicationByNameAndSpace(name string, spaceGUID string) (Application, Warnings, error) { 134 app, warnings, err := actor.CloudControllerClient.GetApplications( 135 ccv2.Filter{ 136 Type: constant.NameFilter, 137 Operator: constant.EqualOperator, 138 Values: []string{name}, 139 }, 140 ccv2.Filter{ 141 Type: constant.SpaceGUIDFilter, 142 Operator: constant.EqualOperator, 143 Values: []string{spaceGUID}, 144 }, 145 ) 146 147 if err != nil { 148 return Application{}, Warnings(warnings), err 149 } 150 151 if len(app) == 0 { 152 return Application{}, Warnings(warnings), actionerror.ApplicationNotFoundError{ 153 Name: name, 154 } 155 } 156 157 return Application(app[0]), Warnings(warnings), nil 158 } 159 160 // GetApplicationsBySpace returns all applications in a space. 161 func (actor Actor) GetApplicationsBySpace(spaceGUID string) ([]Application, Warnings, error) { 162 ccv2Apps, warnings, err := actor.CloudControllerClient.GetApplications( 163 ccv2.Filter{ 164 Type: constant.SpaceGUIDFilter, 165 Operator: constant.EqualOperator, 166 Values: []string{spaceGUID}, 167 }, 168 ) 169 170 if err != nil { 171 return []Application{}, Warnings(warnings), err 172 } 173 174 apps := make([]Application, len(ccv2Apps)) 175 for i, ccv2App := range ccv2Apps { 176 apps[i] = Application(ccv2App) 177 } 178 179 return apps, Warnings(warnings), nil 180 } 181 182 // GetRouteApplications returns a list of apps associated with the provided 183 // Route GUID. 184 func (actor Actor) GetRouteApplications(routeGUID string) ([]Application, Warnings, error) { 185 apps, warnings, err := actor.CloudControllerClient.GetRouteApplications(routeGUID) 186 if err != nil { 187 return nil, Warnings(warnings), err 188 } 189 allApplications := []Application{} 190 for _, app := range apps { 191 allApplications = append(allApplications, Application(app)) 192 } 193 return allApplications, Warnings(warnings), nil 194 } 195 196 // SetApplicationHealthCheckTypeByNameAndSpace updates an application's health 197 // check type if it is not already the desired type. 198 func (actor Actor) SetApplicationHealthCheckTypeByNameAndSpace(name string, spaceGUID string, healthCheckType constant.ApplicationHealthCheckType, httpEndpoint string) (Application, Warnings, error) { 199 if httpEndpoint != "/" && healthCheckType != constant.ApplicationHealthCheckHTTP { 200 return Application{}, nil, actionerror.HTTPHealthCheckInvalidError{} 201 } 202 203 var allWarnings Warnings 204 205 app, warnings, err := actor.GetApplicationByNameAndSpace(name, spaceGUID) 206 allWarnings = append(allWarnings, warnings...) 207 208 if err != nil { 209 return Application{}, allWarnings, err 210 } 211 212 if app.HealthCheckType != healthCheckType || 213 healthCheckType == constant.ApplicationHealthCheckHTTP && app.HealthCheckHTTPEndpoint != httpEndpoint { 214 var healthCheckEndpoint string 215 if healthCheckType == constant.ApplicationHealthCheckHTTP { 216 healthCheckEndpoint = httpEndpoint 217 } 218 219 updatedApp, apiWarnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 220 GUID: app.GUID, 221 HealthCheckType: healthCheckType, 222 HealthCheckHTTPEndpoint: healthCheckEndpoint, 223 }) 224 225 allWarnings = append(allWarnings, Warnings(apiWarnings)...) 226 return Application(updatedApp), allWarnings, err 227 } 228 229 return app, allWarnings, nil 230 } 231 232 // StartApplication restarts a given application. If already stopped, no stop 233 // call will be sent. 234 func (actor Actor) StartApplication(app Application) (<-chan ApplicationStateChange, <-chan string, <-chan error) { 235 236 appState := make(chan ApplicationStateChange) 237 allWarnings := make(chan string) 238 errs := make(chan error) 239 go func() { 240 defer close(appState) 241 defer close(allWarnings) 242 defer close(errs) 243 244 if app.PackageState != constant.ApplicationPackageStaged { 245 appState <- ApplicationStateStaging 246 } 247 248 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 249 GUID: app.GUID, 250 State: constant.ApplicationStarted, 251 }) 252 253 for _, warning := range warnings { 254 allWarnings <- warning 255 } 256 if err != nil { 257 errs <- err 258 return 259 } 260 261 actor.waitForApplicationStageAndStart(Application(updatedApp), appState, allWarnings, errs) 262 }() 263 264 return appState, allWarnings, errs 265 } 266 267 // RestartApplication restarts a given application. If already stopped, no stop 268 // call will be sent. 269 func (actor Actor) RestartApplication(app Application) (<-chan ApplicationStateChange, <-chan string, <-chan error) { 270 271 appState := make(chan ApplicationStateChange) 272 allWarnings := make(chan string) 273 errs := make(chan error) 274 go func() { 275 defer close(appState) 276 defer close(allWarnings) 277 defer close(errs) 278 279 if app.Started() { 280 appState <- ApplicationStateStopping 281 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 282 GUID: app.GUID, 283 State: constant.ApplicationStopped, 284 }) 285 for _, warning := range warnings { 286 allWarnings <- warning 287 } 288 if err != nil { 289 errs <- err 290 return 291 } 292 app = Application(updatedApp) 293 } 294 295 if app.PackageState != constant.ApplicationPackageStaged { 296 appState <- ApplicationStateStaging 297 } 298 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 299 GUID: app.GUID, 300 State: constant.ApplicationStarted, 301 }) 302 303 for _, warning := range warnings { 304 allWarnings <- warning 305 } 306 if err != nil { 307 errs <- err 308 return 309 } 310 311 actor.waitForApplicationStageAndStart(Application(updatedApp), appState, allWarnings, errs) 312 }() 313 314 return appState, allWarnings, errs 315 } 316 317 // RestageApplication restarts a given application. If already stopped, no stop 318 // call will be sent. 319 func (actor Actor) RestageApplication(app Application) (<-chan ApplicationStateChange, <-chan string, <-chan error) { 320 appState := make(chan ApplicationStateChange) 321 allWarnings := make(chan string) 322 errs := make(chan error) 323 go func() { 324 defer close(appState) 325 defer close(allWarnings) 326 defer close(errs) 327 328 appState <- ApplicationStateStaging 329 restagedApp, warnings, err := actor.CloudControllerClient.RestageApplication(ccv2.Application{ 330 GUID: app.GUID, 331 }) 332 333 for _, warning := range warnings { 334 allWarnings <- warning 335 } 336 if err != nil { 337 errs <- err 338 return 339 } 340 341 actor.waitForApplicationStageAndStart(Application(restagedApp), appState, allWarnings, errs) 342 }() 343 344 return appState, allWarnings, errs 345 } 346 347 // UpdateApplication updates an application. 348 func (actor Actor) UpdateApplication(application Application) (Application, Warnings, error) { 349 app, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application(application)) 350 return Application(app), Warnings(warnings), err 351 } 352 353 func (actor Actor) pollStaging(app Application, allWarnings chan<- string) error { 354 timeout := time.Now().Add(actor.Config.StagingTimeout()) 355 for time.Now().Before(timeout) { 356 currentApplication, warnings, err := actor.GetApplication(app.GUID) 357 for _, warning := range warnings { 358 allWarnings <- warning 359 } 360 361 switch { 362 case err != nil: 363 return err 364 case currentApplication.StagingCompleted(): 365 return nil 366 case currentApplication.StagingFailed(): 367 if currentApplication.StagingFailedNoAppDetected() { 368 return actionerror.StagingFailedNoAppDetectedError{Reason: currentApplication.StagingFailedMessage()} 369 } 370 return actionerror.StagingFailedError{Reason: currentApplication.StagingFailedMessage()} 371 } 372 time.Sleep(actor.Config.PollingInterval()) 373 } 374 return actionerror.StagingTimeoutError{AppName: app.Name, Timeout: actor.Config.StagingTimeout()} 375 } 376 377 func (actor Actor) pollStartup(app Application, allWarnings chan<- string) error { 378 timeout := time.Now().Add(actor.Config.StartupTimeout()) 379 for time.Now().Before(timeout) { 380 currentInstances, warnings, err := actor.GetApplicationInstancesByApplication(app.GUID) 381 for _, warning := range warnings { 382 allWarnings <- warning 383 } 384 if err != nil { 385 return err 386 } 387 388 for _, instance := range currentInstances { 389 switch { 390 case instance.Running(): 391 return nil 392 case instance.Crashed(): 393 return actionerror.ApplicationInstanceCrashedError{Name: app.Name} 394 case instance.Flapping(): 395 return actionerror.ApplicationInstanceFlappingError{Name: app.Name} 396 } 397 } 398 time.Sleep(actor.Config.PollingInterval()) 399 } 400 401 return actionerror.StartupTimeoutError{Name: app.Name} 402 } 403 404 func (actor Actor) waitForApplicationStageAndStart(app Application, appState chan ApplicationStateChange, allWarnings chan string, errs chan error) { 405 err := actor.pollStaging(app, allWarnings) 406 if err != nil { 407 errs <- err 408 return 409 } 410 411 if app.Instances.Value == 0 { 412 return 413 } 414 415 appState <- ApplicationStateStarting 416 417 err = actor.pollStartup(app, allWarnings) 418 if err != nil { 419 errs <- err 420 } 421 }