github.com/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  }