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