github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/errors.go (about) 1 package ccv3 2 3 import ( 4 "encoding/json" 5 "net/http" 6 "regexp" 7 "strings" 8 9 "code.cloudfoundry.org/cli/api/cloudcontroller" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 11 ) 12 13 const ( 14 taskWorkersUnavailable = "CF-TaskWorkersUnavailable" 15 operationInProgress = "CF-AsyncServiceInstanceOperationInProgress" 16 ) 17 18 // errorWrapper is the wrapper that converts responses with 4xx and 5xx status 19 // codes to an error. 20 type errorWrapper struct { 21 connection cloudcontroller.Connection 22 } 23 24 func newErrorWrapper() *errorWrapper { 25 return new(errorWrapper) 26 } 27 28 // Make creates a connection in the wrapped connection and handles errors 29 // that it returns. 30 func (e *errorWrapper) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error { 31 err := e.connection.Make(request, passedResponse) 32 33 if rawHTTPStatusErr, ok := err.(ccerror.RawHTTPStatusError); ok { 34 if rawHTTPStatusErr.StatusCode >= http.StatusInternalServerError { 35 return convert500(rawHTTPStatusErr) 36 } 37 return convert400(rawHTTPStatusErr, request) 38 } 39 return err 40 } 41 42 // Wrap wraps a Cloud Controller connection in this error handling wrapper. 43 func (e *errorWrapper) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection { 44 e.connection = innerconnection 45 return e 46 } 47 48 func convert400(rawHTTPStatusErr ccerror.RawHTTPStatusError, request *cloudcontroller.Request) error { 49 firstErr, errorResponse, err := unmarshalFirstV3Error(rawHTTPStatusErr) 50 if err != nil { 51 return err 52 } 53 54 if len(errorResponse.Errors) > 1 { 55 return ccerror.MultiError{Errors: errorResponse.Errors, ResponseCode: rawHTTPStatusErr.StatusCode} 56 } 57 58 switch rawHTTPStatusErr.StatusCode { 59 case http.StatusBadRequest: // 400 60 return handleBadRequest(firstErr, request) 61 case http.StatusUnauthorized: // 401 62 if firstErr.Title == "CF-InvalidAuthToken" { 63 return ccerror.InvalidAuthTokenError{Message: firstErr.Detail} 64 } 65 return ccerror.UnauthorizedError{Message: firstErr.Detail} 66 case http.StatusForbidden: // 403 67 return ccerror.ForbiddenError{Message: firstErr.Detail} 68 case http.StatusNotFound: // 404 69 return handleNotFound(firstErr, request) 70 case http.StatusUnprocessableEntity: // 422 71 return handleUnprocessableEntity(firstErr) 72 case http.StatusServiceUnavailable: // 503 73 if firstErr.Title == taskWorkersUnavailable { 74 return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail} 75 } 76 return ccerror.ServiceUnavailableError{Message: firstErr.Detail} 77 case http.StatusConflict: 78 if firstErr.Title == operationInProgress { 79 return ccerror.ServiceInstanceOperationInProgressError{Message: firstErr.Detail} 80 } 81 } 82 83 return ccerror.V3UnexpectedResponseError{ 84 ResponseCode: rawHTTPStatusErr.StatusCode, 85 RequestIDs: rawHTTPStatusErr.RequestIDs, 86 V3ErrorResponse: errorResponse, 87 } 88 } 89 90 func convert500(rawHTTPStatusErr ccerror.RawHTTPStatusError) error { 91 switch rawHTTPStatusErr.StatusCode { 92 case http.StatusServiceUnavailable: // 503 93 firstErr, _, err := unmarshalFirstV3Error(rawHTTPStatusErr) 94 if err != nil { 95 return err 96 } 97 if firstErr.Title == taskWorkersUnavailable { 98 return ccerror.TaskWorkersUnavailableError{Message: firstErr.Detail} 99 } 100 return ccerror.ServiceUnavailableError{Message: firstErr.Detail} 101 default: 102 return ccerror.V3UnexpectedResponseError{ 103 ResponseCode: rawHTTPStatusErr.StatusCode, 104 RequestIDs: rawHTTPStatusErr.RequestIDs, 105 V3ErrorResponse: ccerror.V3ErrorResponse{ 106 Errors: []ccerror.V3Error{{ 107 Detail: string(rawHTTPStatusErr.RawResponse), 108 }}, 109 }, 110 } 111 } 112 } 113 114 func handleBadRequest(errorResponse ccerror.V3Error, _ *cloudcontroller.Request) error { 115 switch errorResponse.Detail { 116 case "Bad request: Cannot stage package whose state is not ready.": 117 return ccerror.InvalidStateError{} 118 case "This service does not support fetching service instance parameters.": 119 return ccerror.ServiceInstanceParametersFetchNotSupportedError{Message: errorResponse.Detail} 120 default: 121 return ccerror.BadRequestError{Message: errorResponse.Detail} 122 } 123 } 124 125 func handleNotFound(errorResponse ccerror.V3Error, request *cloudcontroller.Request) error { 126 switch errorResponse.Detail { 127 case "App not found": 128 return ccerror.ApplicationNotFoundError{} 129 case "Droplet not found": 130 return ccerror.DropletNotFoundError{} 131 case "Deployment not found": 132 return ccerror.DeploymentNotFoundError{} 133 case "Feature flag not found": 134 return ccerror.FeatureFlagNotFoundError{} 135 case "Instance not found": 136 return ccerror.InstanceNotFoundError{} 137 case "Process not found": 138 return ccerror.ProcessNotFoundError{} 139 case "User not found": 140 return ccerror.UserNotFoundError{} 141 case "Unknown request": 142 return ccerror.APINotFoundError{URL: request.URL.String()} 143 default: 144 return ccerror.ResourceNotFoundError{Message: errorResponse.Detail} 145 } 146 } 147 148 func handleUnprocessableEntity(errorResponse ccerror.V3Error) error { 149 //idea to make route already exist error flexible for all relevant error cases 150 errorString := errorResponse.Detail 151 err := ccerror.UnprocessableEntityError{Message: errorResponse.Detail} 152 appNameTakenRegexp := regexp.MustCompile(`App with the name '.*' already exists\.`) 153 orgNameTakenRegexp := regexp.MustCompile(`Organization '.*' already exists\.`) 154 roleExistsRegexp := regexp.MustCompile(`User '.*' already has '.*' role.*`) 155 quotaExistsRegexp := regexp.MustCompile(`.* Quota '.*' already exists\.`) 156 securityGroupExistsRegexp := regexp.MustCompile(`Security group with name '.*' already exists\.`) 157 158 // boolean switch case with partial/regex string matchers 159 switch { 160 case appNameTakenRegexp.MatchString(errorString) || strings.Contains(errorString, "name must be unique in space"): 161 return ccerror.NameNotUniqueInSpaceError{UnprocessableEntityError: err} 162 case strings.Contains(errorString, 163 "Name must be unique per organization"): 164 return ccerror.NameNotUniqueInOrgError{} 165 case strings.Contains(errorString, 166 "Route already exists"): 167 return ccerror.RouteNotUniqueError{UnprocessableEntityError: err} 168 case strings.Contains(errorString, 169 "Buildpack must be an existing admin buildpack or a valid git URI"): 170 return ccerror.InvalidBuildpackError{} 171 case strings.Contains(errorString, 172 "Assign a droplet before starting this app."): 173 return ccerror.InvalidStartError{} 174 case strings.Contains(errorString, 175 "The service instance name is taken"): 176 return ccerror.ServiceInstanceNameTakenError{Message: err.Message} 177 case orgNameTakenRegexp.MatchString(errorString): 178 return ccerror.OrganizationNameTakenError{UnprocessableEntityError: err} 179 case roleExistsRegexp.MatchString(errorString): 180 return ccerror.RoleAlreadyExistsError{UnprocessableEntityError: err} 181 case quotaExistsRegexp.MatchString(errorString): 182 return ccerror.QuotaAlreadyExists{Message: err.Message} 183 case securityGroupExistsRegexp.MatchString(errorString): 184 return ccerror.SecurityGroupAlreadyExists{Message: err.Message} 185 case strings.Contains(errorString, 186 "Ensure the space is bound to this security group."): 187 return ccerror.SecurityGroupNotBound{Message: err.Message} 188 case errorResponse.Title == "CF-ServiceInstanceAlreadyBoundToSameRoute": 189 return ccerror.ResourceAlreadyExistsError{Message: err.Message} 190 case strings.Contains(errorString, 191 "The app is already bound to the service instance"): 192 return ccerror.ResourceAlreadyExistsError{Message: err.Message} 193 case strings.Contains(errorString, 194 "Key binding names must be unique"): 195 return ccerror.ServiceKeyTakenError{Message: err.Message} 196 default: 197 return err 198 } 199 } 200 201 func unmarshalFirstV3Error(rawHTTPStatusErr ccerror.RawHTTPStatusError) (ccerror.V3Error, ccerror.V3ErrorResponse, error) { 202 // Try to unmarshal the raw error into a CC error. If unmarshaling fails, 203 // return the raw error. 204 var errorResponse ccerror.V3ErrorResponse 205 err := json.Unmarshal(rawHTTPStatusErr.RawResponse, &errorResponse) 206 if err != nil { 207 return ccerror.V3Error{}, errorResponse, ccerror.UnknownHTTPSourceError{ 208 StatusCode: rawHTTPStatusErr.StatusCode, 209 RawResponse: rawHTTPStatusErr.RawResponse, 210 } 211 } 212 213 errors := errorResponse.Errors 214 if len(errors) == 0 { 215 return ccerror.V3Error{}, errorResponse, ccerror.V3UnexpectedResponseError{ 216 ResponseCode: rawHTTPStatusErr.StatusCode, 217 V3ErrorResponse: errorResponse, 218 } 219 } 220 221 return errors[0], errorResponse, nil 222 }