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