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  }