github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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 IsSet: %t, Buildpack: '%s', Command IsSet: %t, Command: '%s', Detected Buildpack IsSet: %t, Detected Buildpack: '%s', Detected Start Command IsSet: %t, Detected Start Command: '%s', Disk Quota: '%d', Docker Image: '%s', Health Check HTTP Endpoint: '%s', Health Check Timeout: '%d', Health Check Type: '%s', Instances IsSet: %t, Instances: '%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, client NOAAClient) (<-chan *LogMessage, <-chan error, <-chan ApplicationStateChange, <-chan string, <-chan error) { 235 messages, logErrs := actor.GetStreamingLogs(app.GUID, client) 236 237 appState := make(chan ApplicationStateChange) 238 allWarnings := make(chan string) 239 errs := make(chan error) 240 go func() { 241 defer close(appState) 242 defer close(allWarnings) 243 defer close(errs) 244 defer client.Close() // automatic close to prevent stale clients 245 246 if app.PackageState != constant.ApplicationPackageStaged { 247 appState <- ApplicationStateStaging 248 } 249 250 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 251 GUID: app.GUID, 252 State: constant.ApplicationStarted, 253 }) 254 255 for _, warning := range warnings { 256 allWarnings <- warning 257 } 258 if err != nil { 259 errs <- err 260 return 261 } 262 263 actor.waitForApplicationStageAndStart(Application(updatedApp), client, appState, allWarnings, errs) 264 }() 265 266 return messages, logErrs, appState, allWarnings, errs 267 } 268 269 // RestartApplication restarts a given application. If already stopped, no stop 270 // call will be sent. 271 func (actor Actor) RestartApplication(app Application, client NOAAClient) (<-chan *LogMessage, <-chan error, <-chan ApplicationStateChange, <-chan string, <-chan error) { 272 messages, logErrs := actor.GetStreamingLogs(app.GUID, client) 273 274 appState := make(chan ApplicationStateChange) 275 allWarnings := make(chan string) 276 errs := make(chan error) 277 go func() { 278 defer close(appState) 279 defer close(allWarnings) 280 defer close(errs) 281 defer client.Close() // automatic close to prevent stale clients 282 283 if app.Started() { 284 appState <- ApplicationStateStopping 285 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 286 GUID: app.GUID, 287 State: constant.ApplicationStopped, 288 }) 289 for _, warning := range warnings { 290 allWarnings <- warning 291 } 292 if err != nil { 293 errs <- err 294 return 295 } 296 app = Application(updatedApp) 297 } 298 299 if app.PackageState != constant.ApplicationPackageStaged { 300 appState <- ApplicationStateStaging 301 } 302 updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application{ 303 GUID: app.GUID, 304 State: constant.ApplicationStarted, 305 }) 306 307 for _, warning := range warnings { 308 allWarnings <- warning 309 } 310 if err != nil { 311 errs <- err 312 return 313 } 314 315 actor.waitForApplicationStageAndStart(Application(updatedApp), client, appState, allWarnings, errs) 316 }() 317 318 return messages, logErrs, appState, allWarnings, errs 319 } 320 321 // RestageApplication restarts a given application. If already stopped, no stop 322 // call will be sent. 323 func (actor Actor) RestageApplication(app Application, client NOAAClient) (<-chan *LogMessage, <-chan error, <-chan ApplicationStateChange, <-chan string, <-chan error) { 324 messages, logErrs := actor.GetStreamingLogs(app.GUID, client) 325 326 appState := make(chan ApplicationStateChange) 327 allWarnings := make(chan string) 328 errs := make(chan error) 329 go func() { 330 defer close(appState) 331 defer close(allWarnings) 332 defer close(errs) 333 defer client.Close() // automatic close to prevent stale clients 334 335 appState <- ApplicationStateStaging 336 restagedApp, warnings, err := actor.CloudControllerClient.RestageApplication(ccv2.Application{ 337 GUID: app.GUID, 338 }) 339 340 for _, warning := range warnings { 341 allWarnings <- warning 342 } 343 if err != nil { 344 errs <- err 345 return 346 } 347 348 actor.waitForApplicationStageAndStart(Application(restagedApp), client, appState, allWarnings, errs) 349 }() 350 351 return messages, logErrs, appState, allWarnings, errs 352 } 353 354 // UpdateApplication updates an application. 355 func (actor Actor) UpdateApplication(application Application) (Application, Warnings, error) { 356 app, warnings, err := actor.CloudControllerClient.UpdateApplication(ccv2.Application(application)) 357 return Application(app), Warnings(warnings), err 358 } 359 360 func (actor Actor) pollStaging(app Application, allWarnings chan<- string) error { 361 timeout := time.Now().Add(actor.Config.StagingTimeout()) 362 for time.Now().Before(timeout) { 363 currentApplication, warnings, err := actor.GetApplication(app.GUID) 364 for _, warning := range warnings { 365 allWarnings <- warning 366 } 367 368 switch { 369 case err != nil: 370 return err 371 case currentApplication.StagingCompleted(): 372 return nil 373 case currentApplication.StagingFailed(): 374 if currentApplication.StagingFailedNoAppDetected() { 375 return actionerror.StagingFailedNoAppDetectedError{Reason: currentApplication.StagingFailedMessage()} 376 } 377 return actionerror.StagingFailedError{Reason: currentApplication.StagingFailedMessage()} 378 } 379 time.Sleep(actor.Config.PollingInterval()) 380 } 381 return actionerror.StagingTimeoutError{AppName: app.Name, Timeout: actor.Config.StagingTimeout()} 382 } 383 384 func (actor Actor) pollStartup(app Application, allWarnings chan<- string) error { 385 timeout := time.Now().Add(actor.Config.StartupTimeout()) 386 for time.Now().Before(timeout) { 387 currentInstances, warnings, err := actor.GetApplicationInstancesByApplication(app.GUID) 388 for _, warning := range warnings { 389 allWarnings <- warning 390 } 391 if err != nil { 392 return err 393 } 394 395 for _, instance := range currentInstances { 396 switch { 397 case instance.Running(): 398 return nil 399 case instance.Crashed(): 400 return actionerror.ApplicationInstanceCrashedError{Name: app.Name} 401 case instance.Flapping(): 402 return actionerror.ApplicationInstanceFlappingError{Name: app.Name} 403 } 404 } 405 time.Sleep(actor.Config.PollingInterval()) 406 } 407 408 return actionerror.StartupTimeoutError{Name: app.Name} 409 } 410 411 func (actor Actor) waitForApplicationStageAndStart(app Application, client NOAAClient, appState chan ApplicationStateChange, allWarnings chan string, errs chan error) { 412 err := actor.pollStaging(app, allWarnings) 413 if err != nil { 414 errs <- err 415 return 416 } 417 418 if app.Instances.Value == 0 { 419 return 420 } 421 422 client.Close() // Explicit close to stop logs from displaying on the screen 423 appState <- ApplicationStateStarting 424 425 err = actor.pollStartup(app, allWarnings) 426 if err != nil { 427 errs <- err 428 } 429 }