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 }