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  }