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  }