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