github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/job.go (about)

     1  package ccv3
     2  
     3  import (
     4  	"time"
     5  
     6  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
     7  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
     8  )
     9  
    10  // Job represents a Cloud Controller Job.
    11  type Job struct {
    12  	// RawErrors is a list of errors that occurred while processing the job.
    13  	RawErrors []JobErrorDetails `json:"errors"`
    14  	// GUID is a unique identifier for the job.
    15  	GUID string `json:"guid"`
    16  	// State is the state of the job.
    17  	State constant.JobState `json:"state"`
    18  	// Warnings are the warnings emitted by the job during its processing.
    19  	Warnings []jobWarning `json:"warnings"`
    20  }
    21  
    22  // Errors returns back a list of
    23  func (job Job) Errors() []error {
    24  	var errs []error
    25  	for _, errDetails := range job.RawErrors {
    26  		switch errDetails.Code {
    27  		case constant.JobErrorCodeBuildpackAlreadyExistsForStack:
    28  			errs = append(errs, ccerror.BuildpackAlreadyExistsForStackError{Message: errDetails.Detail})
    29  		case constant.JobErrorCodeBuildpackInvalid:
    30  			errs = append(errs, ccerror.BuildpackInvalidError{Message: errDetails.Detail})
    31  		case constant.JobErrorCodeBuildpackStacksDontMatch:
    32  			errs = append(errs, ccerror.BuildpackStacksDontMatchError{Message: errDetails.Detail})
    33  		case constant.JobErrorCodeBuildpackStackDoesNotExist:
    34  			errs = append(errs, ccerror.BuildpackStackDoesNotExistError{Message: errDetails.Detail})
    35  		case constant.JobErrorCodeBuildpackZipInvalid:
    36  			errs = append(errs, ccerror.BuildpackZipInvalidError{Message: errDetails.Detail})
    37  		default:
    38  			errs = append(errs, ccerror.V3JobFailedError{
    39  				JobGUID: job.GUID,
    40  				Code:    errDetails.Code,
    41  				Detail:  errDetails.Detail,
    42  				Title:   errDetails.Title,
    43  			})
    44  		}
    45  	}
    46  	return errs
    47  }
    48  
    49  // HasFailed returns true when the job has completed with an error/failure.
    50  func (job Job) HasFailed() bool {
    51  	return job.State == constant.JobFailed
    52  }
    53  
    54  // IsComplete returns true when the job has completed successfully.
    55  func (job Job) IsComplete() bool {
    56  	return job.State == constant.JobComplete
    57  }
    58  
    59  // IsAt returns true when the job has reached the desired state.
    60  func (job Job) IsAt(state constant.JobState) bool {
    61  	return job.State == state
    62  }
    63  
    64  type jobWarning struct {
    65  	Detail string `json:"detail"`
    66  }
    67  
    68  // JobErrorDetails provides information regarding a job's error.
    69  type JobErrorDetails struct {
    70  	// Code is a numeric code for this error.
    71  	Code constant.JobErrorCode `json:"code"`
    72  	// Detail is a verbose description of the error.
    73  	Detail string `json:"detail"`
    74  	// Title is a short description of the error.
    75  	Title string `json:"title"`
    76  }
    77  
    78  // GetJob returns a job for the provided GUID.
    79  func (client *Client) GetJob(jobURL JobURL) (Job, Warnings, error) {
    80  	var responseBody Job
    81  
    82  	_, warnings, err := client.MakeRequest(RequestParams{
    83  		URL:          string(jobURL),
    84  		ResponseBody: &responseBody,
    85  	})
    86  
    87  	for _, jobWarning := range responseBody.Warnings {
    88  		warnings = append(warnings, jobWarning.Detail)
    89  	}
    90  
    91  	return responseBody, warnings, err
    92  }
    93  
    94  // PollJob will keep polling the given job until the job has terminated, an
    95  // error is encountered, or config.OverallPollingTimeout is reached. In the
    96  // last case, a JobTimeoutError is returned.
    97  func (client *Client) PollJob(jobURL JobURL) (Warnings, error) {
    98  	return client.PollJobForState(jobURL, constant.JobComplete)
    99  }
   100  
   101  func (client *Client) PollJobForState(jobURL JobURL, state constant.JobState) (Warnings, error) {
   102  	if jobURL == "" {
   103  		return nil, nil
   104  	}
   105  
   106  	var (
   107  		err         error
   108  		warnings    Warnings
   109  		allWarnings Warnings
   110  		job         Job
   111  	)
   112  
   113  	startTime := client.clock.Now()
   114  	for client.clock.Now().Sub(startTime) < client.jobPollingTimeout {
   115  		job, warnings, err = client.GetJob(jobURL)
   116  		allWarnings = append(allWarnings, warnings...)
   117  		if err != nil {
   118  			return allWarnings, err
   119  		}
   120  
   121  		if job.HasFailed() {
   122  			if len(job.Errors()) > 0 {
   123  				firstError := job.Errors()[0]
   124  				return allWarnings, firstError
   125  			} else {
   126  				return allWarnings, ccerror.JobFailedNoErrorError{
   127  					JobGUID: job.GUID,
   128  				}
   129  			}
   130  		}
   131  
   132  		if job.IsComplete() {
   133  			return allWarnings, nil
   134  		}
   135  
   136  		if job.IsAt(state) {
   137  			return allWarnings, nil
   138  		}
   139  
   140  		time.Sleep(client.jobPollingInterval)
   141  	}
   142  
   143  	return allWarnings, ccerror.JobTimeoutError{
   144  		JobGUID: job.GUID,
   145  		Timeout: client.jobPollingTimeout,
   146  	}
   147  }
   148  
   149  type PollJobEvent struct {
   150  	State    constant.JobState
   151  	Err      error
   152  	Warnings Warnings
   153  }
   154  
   155  func (client *Client) PollJobToEventStream(jobURL JobURL) chan PollJobEvent {
   156  	stream := make(chan PollJobEvent)
   157  
   158  	if jobURL == "" {
   159  		close(stream)
   160  		return stream
   161  	}
   162  
   163  	go func() {
   164  		var end bool
   165  
   166  		startTime := client.clock.Now()
   167  		for !end {
   168  			job, warnings, err := client.GetJob(jobURL)
   169  			event := PollJobEvent{
   170  				State:    job.State,
   171  				Err:      err,
   172  				Warnings: warnings,
   173  			}
   174  
   175  			switch {
   176  			case event.Err != nil:
   177  				end = true
   178  			case job.IsComplete():
   179  				end = true
   180  			case job.HasFailed():
   181  				event.Err = job.Errors()[0]
   182  				end = true
   183  			case client.clock.Now().Sub(startTime) > client.jobPollingTimeout:
   184  				event.Err = ccerror.JobTimeoutError{
   185  					JobGUID: job.GUID,
   186  					Timeout: client.jobPollingTimeout,
   187  				}
   188  				end = true
   189  			}
   190  
   191  			stream <- event
   192  			time.Sleep(client.jobPollingInterval)
   193  		}
   194  
   195  		close(stream)
   196  	}()
   197  
   198  	return stream
   199  }