github.com/lukasheimann/cloudfoundrycli@v7.1.0+incompatible/actor/v7action/application.go (about)

     1  package v7action
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    12  	"code.cloudfoundry.org/cli/resources"
    13  )
    14  
    15  func (actor Actor) DeleteApplicationByNameAndSpace(name, spaceGUID string, deleteRoutes bool) (Warnings, error) {
    16  	var allWarnings Warnings
    17  	var jobQueue []ccv3.JobURL
    18  
    19  	app, getAppWarnings, err := actor.GetApplicationByNameAndSpace(name, spaceGUID)
    20  	allWarnings = append(allWarnings, getAppWarnings...)
    21  	if err != nil {
    22  		return allWarnings, err
    23  	}
    24  
    25  	var routes []resources.Route
    26  	if deleteRoutes {
    27  		var getRoutesWarnings Warnings
    28  		routes, getRoutesWarnings, err = actor.GetApplicationRoutes(app.GUID)
    29  		allWarnings = append(allWarnings, getRoutesWarnings...)
    30  		if err != nil {
    31  			return allWarnings, err
    32  		}
    33  
    34  		for _, route := range routes {
    35  			if len(route.Destinations) > 1 {
    36  				for _, destination := range route.Destinations {
    37  					guid := destination.App.GUID
    38  					if guid != app.GUID {
    39  						return allWarnings, actionerror.RouteBoundToMultipleAppsError{AppName: app.Name, RouteURL: route.URL}
    40  					}
    41  				}
    42  			}
    43  		}
    44  	}
    45  
    46  	appDeleteJobURL, deleteAppWarnings, err := actor.CloudControllerClient.DeleteApplication(app.GUID)
    47  	allWarnings = append(allWarnings, deleteAppWarnings...)
    48  	if err != nil {
    49  		return allWarnings, err
    50  	}
    51  
    52  	pollWarnings, err := actor.CloudControllerClient.PollJob(appDeleteJobURL)
    53  	allWarnings = append(allWarnings, pollWarnings...)
    54  	if err != nil {
    55  		return allWarnings, err
    56  	}
    57  
    58  	if deleteRoutes {
    59  		for _, route := range routes {
    60  			jobURL, deleteRouteWarnings, err := actor.CloudControllerClient.DeleteRoute(route.GUID)
    61  			allWarnings = append(allWarnings, deleteRouteWarnings...)
    62  			if err != nil {
    63  				if _, ok := err.(ccerror.ResourceNotFoundError); ok {
    64  					continue
    65  				}
    66  				return allWarnings, err
    67  			}
    68  
    69  			jobQueue = append(jobQueue, jobURL)
    70  		}
    71  	}
    72  
    73  	for _, job := range jobQueue {
    74  		pollWarnings, err := actor.CloudControllerClient.PollJob(job)
    75  		allWarnings = append(allWarnings, pollWarnings...)
    76  		if err != nil {
    77  			return allWarnings, err
    78  		}
    79  	}
    80  
    81  	return allWarnings, err
    82  }
    83  
    84  func (actor Actor) GetApplicationsByGUIDs(appGUIDs []string) ([]resources.Application, Warnings, error) {
    85  	uniqueAppGUIDs := map[string]bool{}
    86  	for _, appGUID := range appGUIDs {
    87  		uniqueAppGUIDs[appGUID] = true
    88  	}
    89  
    90  	apps, warnings, err := actor.CloudControllerClient.GetApplications(
    91  		ccv3.Query{Key: ccv3.GUIDFilter, Values: appGUIDs},
    92  	)
    93  
    94  	if err != nil {
    95  		return nil, Warnings(warnings), err
    96  	}
    97  
    98  	if len(apps) < len(uniqueAppGUIDs) {
    99  		return nil, Warnings(warnings), actionerror.ApplicationsNotFoundError{}
   100  	}
   101  
   102  	return apps, Warnings(warnings), nil
   103  }
   104  
   105  func (actor Actor) GetApplicationsByNamesAndSpace(appNames []string, spaceGUID string) ([]resources.Application, Warnings, error) {
   106  	uniqueAppNames := map[string]bool{}
   107  	for _, appName := range appNames {
   108  		uniqueAppNames[appName] = true
   109  	}
   110  
   111  	apps, warnings, err := actor.CloudControllerClient.GetApplications(
   112  		ccv3.Query{Key: ccv3.NameFilter, Values: appNames},
   113  		ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}},
   114  	)
   115  
   116  	if err != nil {
   117  		return nil, Warnings(warnings), err
   118  	}
   119  
   120  	if len(apps) < len(uniqueAppNames) {
   121  		return nil, Warnings(warnings), actionerror.ApplicationsNotFoundError{}
   122  	}
   123  
   124  	return apps, Warnings(warnings), nil
   125  }
   126  
   127  // GetApplicationByNameAndSpace returns the application with the given
   128  // name in the given space.
   129  func (actor Actor) GetApplicationByNameAndSpace(appName string, spaceGUID string) (resources.Application, Warnings, error) {
   130  	apps, warnings, err := actor.GetApplicationsByNamesAndSpace([]string{appName}, spaceGUID)
   131  
   132  	if err != nil {
   133  		if _, ok := err.(actionerror.ApplicationsNotFoundError); ok {
   134  			return resources.Application{}, warnings, actionerror.ApplicationNotFoundError{Name: appName}
   135  		}
   136  		return resources.Application{}, warnings, err
   137  	}
   138  
   139  	return apps[0], warnings, nil
   140  }
   141  
   142  // GetApplicationsBySpace returns all applications in a space.
   143  func (actor Actor) GetApplicationsBySpace(spaceGUID string) ([]resources.Application, Warnings, error) {
   144  	apps, warnings, err := actor.CloudControllerClient.GetApplications(
   145  		ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}},
   146  	)
   147  
   148  	if err != nil {
   149  		return []resources.Application{}, Warnings(warnings), err
   150  	}
   151  
   152  	return apps, Warnings(warnings), nil
   153  }
   154  
   155  // CreateApplicationInSpace creates and returns the application with the given
   156  // name in the given space.
   157  func (actor Actor) CreateApplicationInSpace(app resources.Application, spaceGUID string) (resources.Application, Warnings, error) {
   158  	createdApp, warnings, err := actor.CloudControllerClient.CreateApplication(
   159  		resources.Application{
   160  			LifecycleType:       app.LifecycleType,
   161  			LifecycleBuildpacks: app.LifecycleBuildpacks,
   162  			StackName:           app.StackName,
   163  			Name:                app.Name,
   164  			SpaceGUID:           spaceGUID,
   165  		})
   166  
   167  	if err != nil {
   168  		return resources.Application{}, Warnings(warnings), err
   169  	}
   170  
   171  	return createdApp, Warnings(warnings), nil
   172  }
   173  
   174  // SetApplicationProcessHealthCheckTypeByNameAndSpace sets the health check
   175  // information of the provided processType for an application with the given
   176  // name and space GUID.
   177  func (actor Actor) SetApplicationProcessHealthCheckTypeByNameAndSpace(
   178  	appName string,
   179  	spaceGUID string,
   180  	healthCheckType constant.HealthCheckType,
   181  	httpEndpoint string,
   182  	processType string,
   183  	invocationTimeout int64,
   184  ) (resources.Application, Warnings, error) {
   185  
   186  	app, getWarnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID)
   187  	if err != nil {
   188  		return resources.Application{}, getWarnings, err
   189  	}
   190  
   191  	setWarnings, err := actor.UpdateProcessByTypeAndApplication(
   192  		processType,
   193  		app.GUID,
   194  		Process{
   195  			HealthCheckType:              healthCheckType,
   196  			HealthCheckEndpoint:          httpEndpoint,
   197  			HealthCheckInvocationTimeout: invocationTimeout,
   198  		})
   199  	return app, append(getWarnings, setWarnings...), err
   200  }
   201  
   202  // StopApplication stops an application.
   203  func (actor Actor) StopApplication(appGUID string) (Warnings, error) {
   204  	_, warnings, err := actor.CloudControllerClient.UpdateApplicationStop(appGUID)
   205  
   206  	return Warnings(warnings), err
   207  }
   208  
   209  // StartApplication starts an application.
   210  func (actor Actor) StartApplication(appGUID string) (Warnings, error) {
   211  	_, warnings, err := actor.CloudControllerClient.UpdateApplicationStart(appGUID)
   212  	return Warnings(warnings), err
   213  }
   214  
   215  // RestartApplication restarts an application and waits for it to start.
   216  func (actor Actor) RestartApplication(appGUID string, noWait bool) (Warnings, error) {
   217  	// var allWarnings Warnings
   218  	_, warnings, err := actor.CloudControllerClient.UpdateApplicationRestart(appGUID)
   219  	return Warnings(warnings), err
   220  }
   221  
   222  func (actor Actor) GetUnstagedNewestPackageGUID(appGUID string) (string, Warnings, error) {
   223  	var err error
   224  	var allWarnings Warnings
   225  	packages, warnings, err := actor.CloudControllerClient.GetPackages(
   226  		ccv3.Query{Key: ccv3.AppGUIDFilter, Values: []string{appGUID}},
   227  		ccv3.Query{Key: ccv3.OrderBy, Values: []string{ccv3.CreatedAtDescendingOrder}},
   228  		ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}})
   229  	allWarnings = append(allWarnings, warnings...)
   230  	if err != nil {
   231  		return "", allWarnings, err
   232  	}
   233  	if len(packages) == 0 {
   234  		return "", allWarnings, nil
   235  	}
   236  
   237  	newestPackage := packages[0]
   238  
   239  	droplets, warnings, err := actor.CloudControllerClient.GetPackageDroplets(
   240  		newestPackage.GUID,
   241  		ccv3.Query{Key: ccv3.StatesFilter, Values: []string{"STAGED"}},
   242  		ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}},
   243  	)
   244  	allWarnings = append(allWarnings, warnings...)
   245  	if err != nil {
   246  		return "", allWarnings, err
   247  	}
   248  
   249  	if len(droplets) == 0 {
   250  		return newestPackage.GUID, allWarnings, nil
   251  	}
   252  
   253  	return "", allWarnings, nil
   254  }
   255  
   256  // PollStart polls an application's processes until some are started. If noWait is false,
   257  // it waits for at least one instance of all processes to be running. If noWait is true,
   258  // it only waits for an instance of the web process to be running.
   259  func (actor Actor) PollStart(app resources.Application, noWait bool, handleInstanceDetails func(string)) (Warnings, error) {
   260  	var allWarnings Warnings
   261  	processes, warnings, err := actor.CloudControllerClient.GetApplicationProcesses(app.GUID)
   262  	allWarnings = append(allWarnings, warnings...)
   263  	if err != nil {
   264  		return allWarnings, err
   265  	}
   266  
   267  	var filteredProcesses []ccv3.Process
   268  	if noWait {
   269  		for _, process := range processes {
   270  			if process.Type == constant.ProcessTypeWeb {
   271  				filteredProcesses = append(filteredProcesses, process)
   272  			}
   273  		}
   274  	} else {
   275  		filteredProcesses = processes
   276  	}
   277  
   278  	timer := actor.Clock.NewTimer(time.Millisecond)
   279  	defer timer.Stop()
   280  	timeout := actor.Clock.After(actor.Config.StartupTimeout())
   281  
   282  	for {
   283  		select {
   284  		case <-timeout:
   285  			return allWarnings, actionerror.StartupTimeoutError{Name: app.Name}
   286  		case <-timer.C():
   287  			stopPolling, warnings, err := actor.PollProcesses(filteredProcesses, handleInstanceDetails)
   288  			allWarnings = append(allWarnings, warnings...)
   289  			if stopPolling || err != nil {
   290  				return allWarnings, err
   291  			}
   292  
   293  			timer.Reset(actor.Config.PollingInterval())
   294  		}
   295  	}
   296  }
   297  
   298  // PollStartForRolling polls a deploying application's processes until some are started. It does the same thing as PollStart, except it accounts for rolling deployments and whether
   299  // they have failed or been canceled during polling.
   300  func (actor Actor) PollStartForRolling(app resources.Application, deploymentGUID string, noWait bool, handleInstanceDetails func(string)) (Warnings, error) {
   301  	var (
   302  		deployment  ccv3.Deployment
   303  		processes   []ccv3.Process
   304  		allWarnings Warnings
   305  	)
   306  
   307  	timer := actor.Clock.NewTimer(time.Millisecond)
   308  	defer timer.Stop()
   309  	timeout := actor.Clock.After(actor.Config.StartupTimeout())
   310  
   311  	for {
   312  		select {
   313  		case <-timeout:
   314  			return allWarnings, actionerror.StartupTimeoutError{Name: app.Name}
   315  		case <-timer.C():
   316  			if !isDeployed(deployment) {
   317  				ccDeployment, warnings, err := actor.getDeployment(deploymentGUID)
   318  				allWarnings = append(allWarnings, warnings...)
   319  				if err != nil {
   320  					return allWarnings, err
   321  				}
   322  				deployment = ccDeployment
   323  				processes, warnings, err = actor.getProcesses(deployment, app.GUID, noWait)
   324  				allWarnings = append(allWarnings, warnings...)
   325  				if err != nil {
   326  					return allWarnings, err
   327  				}
   328  			}
   329  
   330  			if noWait || isDeployed(deployment) {
   331  				stopPolling, warnings, err := actor.PollProcesses(processes, handleInstanceDetails)
   332  				allWarnings = append(allWarnings, warnings...)
   333  				if stopPolling || err != nil {
   334  					return allWarnings, err
   335  				}
   336  			}
   337  
   338  			timer.Reset(actor.Config.PollingInterval())
   339  		}
   340  	}
   341  }
   342  
   343  func isDeployed(d ccv3.Deployment) bool {
   344  	return d.StatusValue == constant.DeploymentStatusValueFinalized && d.StatusReason == constant.DeploymentStatusReasonDeployed
   345  }
   346  
   347  // PollProcesses - return true if there's no need to keep polling
   348  func (actor Actor) PollProcesses(processes []ccv3.Process, handleInstanceDetails func(string)) (bool, Warnings, error) {
   349  	numProcesses := len(processes)
   350  	numStableProcesses := 0
   351  	var allWarnings Warnings
   352  	for _, process := range processes {
   353  		ccInstances, ccWarnings, err := actor.CloudControllerClient.GetProcessInstances(process.GUID)
   354  		instances := ProcessInstances(ccInstances)
   355  		allWarnings = append(allWarnings, ccWarnings...)
   356  		if err != nil {
   357  			return true, allWarnings, err
   358  		}
   359  
   360  		handleInstanceDetails(formatInstanceDetails(instances))
   361  
   362  		if instances.Empty() || instances.AnyRunning() {
   363  			numStableProcesses += 1
   364  			continue
   365  		}
   366  
   367  		if instances.AllCrashed() {
   368  			return true, allWarnings, actionerror.AllInstancesCrashedError{}
   369  		}
   370  
   371  		//precondition: !instances.Empty() && no instances are running
   372  		// do not increment numStableProcesses
   373  		return false, allWarnings, nil
   374  	}
   375  	return numStableProcesses == numProcesses, allWarnings, nil
   376  }
   377  
   378  // UpdateApplication updates the buildpacks on an application
   379  func (actor Actor) UpdateApplication(app resources.Application) (resources.Application, Warnings, error) {
   380  	ccApp := resources.Application{
   381  		GUID:                app.GUID,
   382  		StackName:           app.StackName,
   383  		LifecycleType:       app.LifecycleType,
   384  		LifecycleBuildpacks: app.LifecycleBuildpacks,
   385  		Metadata:            app.Metadata,
   386  		Name:                app.Name,
   387  	}
   388  
   389  	updatedApp, warnings, err := actor.CloudControllerClient.UpdateApplication(ccApp)
   390  	if err != nil {
   391  		return resources.Application{}, Warnings(warnings), err
   392  	}
   393  
   394  	return updatedApp, Warnings(warnings), nil
   395  }
   396  
   397  func (actor Actor) getDeployment(deploymentGUID string) (ccv3.Deployment, Warnings, error) {
   398  	deployment, warnings, err := actor.CloudControllerClient.GetDeployment(deploymentGUID)
   399  	if err != nil {
   400  		return deployment, Warnings(warnings), err
   401  	}
   402  
   403  	if deployment.StatusValue == constant.DeploymentStatusValueFinalized {
   404  		switch deployment.StatusReason {
   405  		case constant.DeploymentStatusReasonCanceled:
   406  			return deployment, Warnings(warnings), errors.New("Deployment has been canceled")
   407  		case constant.DeploymentStatusReasonSuperseded:
   408  			return deployment, Warnings(warnings), errors.New("Deployment has been superseded")
   409  		}
   410  	}
   411  
   412  	return deployment, Warnings(warnings), err
   413  }
   414  
   415  func (actor Actor) getProcesses(deployment ccv3.Deployment, appGUID string, noWait bool) ([]ccv3.Process, Warnings, error) {
   416  	if noWait {
   417  		// these are only web processes for now so we can just use these
   418  		return deployment.NewProcesses, nil, nil
   419  	}
   420  
   421  	// if the deployment is deployed we know web are all running and PollProcesses will see those as stable
   422  	// so just getting all processes is equivalent to just getting non-web ones and polling those
   423  	if isDeployed(deployment) {
   424  		processes, warnings, err := actor.CloudControllerClient.GetApplicationProcesses(appGUID)
   425  		if err != nil {
   426  			return processes, Warnings(warnings), err
   427  		}
   428  		return processes, Warnings(warnings), nil
   429  	}
   430  
   431  	return nil, nil, nil
   432  }
   433  
   434  func (actor Actor) RenameApplicationByNameAndSpaceGUID(appName, newAppName, spaceGUID string) (resources.Application, Warnings, error) {
   435  	allWarnings := Warnings{}
   436  	application, warnings, err := actor.GetApplicationByNameAndSpace(appName, spaceGUID)
   437  	allWarnings = append(allWarnings, warnings...)
   438  	if err != nil {
   439  		return resources.Application{}, allWarnings, err
   440  	}
   441  	application.Name = newAppName
   442  	application, warnings, err = actor.UpdateApplication(application)
   443  	allWarnings = append(allWarnings, warnings...)
   444  	if err != nil {
   445  		return resources.Application{}, allWarnings, err
   446  	}
   447  
   448  	return application, allWarnings, nil
   449  }
   450  
   451  func formatInstanceDetails(instances ProcessInstances) string {
   452  	for _, instance := range instances {
   453  		if instance.Details != "" {
   454  			return fmt.Sprintf("Error starting instances: '%s'", instance.Details)
   455  		}
   456  	}
   457  	return "Instances starting..."
   458  }