github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/avs/client.go (about) 1 package avs 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "strconv" 12 "strings" 13 14 kebError "github.com/kyma-project/kyma-environment-broker/internal/error" 15 "github.com/sirupsen/logrus" 16 "golang.org/x/oauth2" 17 ) 18 19 type Client struct { 20 httpClient *http.Client 21 avsConfig Config 22 log logrus.FieldLogger 23 ctx context.Context 24 } 25 26 func NewClient(ctx context.Context, avsConfig Config, log logrus.FieldLogger) (*Client, error) { 27 return &Client{ 28 avsConfig: avsConfig, 29 log: log, 30 ctx: ctx, 31 }, nil 32 } 33 34 func (c *Client) CreateEvaluation(evaluationRequest *BasicEvaluationCreateRequest) (*BasicEvaluationCreateResponse, error) { 35 var responseObject BasicEvaluationCreateResponse 36 37 objAsBytes, err := json.Marshal(evaluationRequest) 38 if err != nil { 39 return &responseObject, fmt.Errorf("while marshaling evaluation request: %w", err) 40 } 41 42 request, err := http.NewRequest(http.MethodPost, c.avsConfig.ApiEndpoint, bytes.NewReader(objAsBytes)) 43 if err != nil { 44 return &responseObject, fmt.Errorf("while creating request: %w", err) 45 } 46 request.Header.Set("Content-Type", "application/json") 47 48 response, err := c.execute(request, false, true) 49 if err != nil { 50 return &responseObject, fmt.Errorf("while executing CreateEvaluation request: %w", err) 51 } 52 defer func() { 53 if closeErr := c.closeResponseBody(response); closeErr != nil { 54 err = kebError.AsTemporaryError(closeErr, "while closing CreateEvaluation response") 55 } 56 }() 57 58 err = json.NewDecoder(response.Body).Decode(&responseObject) 59 if err != nil { 60 return nil, fmt.Errorf("while decode create evaluation response: %w", err) 61 } 62 63 return &responseObject, nil 64 } 65 66 func (c *Client) GetEvaluation(evaluationID int64) (*BasicEvaluationCreateResponse, error) { 67 var responseObject BasicEvaluationCreateResponse 68 absoluteURL := appendId(c.avsConfig.ApiEndpoint, evaluationID) 69 70 request, err := http.NewRequest(http.MethodGet, absoluteURL, nil) 71 if err != nil { 72 return &responseObject, fmt.Errorf("while creating request: %w", err) 73 } 74 request.Header.Set("Content-Type", "application/json") 75 76 response, err := c.execute(request, false, true) 77 if err != nil { 78 return &responseObject, fmt.Errorf("while executing GetEvaluation request: %w", err) 79 } 80 defer func() { 81 if closeErr := c.closeResponseBody(response); closeErr != nil { 82 err = kebError.AsTemporaryError(closeErr, "while closing GetEvaluation response") 83 } 84 }() 85 86 err = json.NewDecoder(response.Body).Decode(&responseObject) 87 if err != nil { 88 return nil, fmt.Errorf("while decode create evaluation response: %w", err) 89 } 90 91 return &responseObject, nil 92 } 93 94 func (c *Client) AddTag(evaluationID int64, tag *Tag) (*BasicEvaluationCreateResponse, error) { 95 var responseObject BasicEvaluationCreateResponse 96 97 objAsBytes, err := json.Marshal(tag) 98 if err != nil { 99 return &responseObject, fmt.Errorf("while marshaling AddTag request: %w", err) 100 } 101 absoluteURL := appendId(c.avsConfig.ApiEndpoint, evaluationID) 102 103 request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/tag", absoluteURL), bytes.NewReader(objAsBytes)) 104 if err != nil { 105 return &responseObject, fmt.Errorf("while creating AddTag request: %w", err) 106 } 107 request.Header.Set("Content-Type", "application/json") 108 109 response, err := c.execute(request, false, true) 110 if err != nil { 111 return &responseObject, fmt.Errorf("while executing AddTag request: %w", err) 112 } 113 defer func() { 114 if closeErr := c.closeResponseBody(response); closeErr != nil { 115 err = kebError.AsTemporaryError(closeErr, "while closing AddTag response") 116 } 117 }() 118 119 err = json.NewDecoder(response.Body).Decode(&responseObject) 120 if err != nil { 121 return nil, fmt.Errorf("while decode AddTag response: %w", err) 122 } 123 124 return &responseObject, nil 125 } 126 127 func (c *Client) SetStatus(evaluationID int64, status string) (*BasicEvaluationCreateResponse, error) { 128 var responseObject BasicEvaluationCreateResponse 129 130 objAsBytes, err := json.Marshal(status) 131 if err != nil { 132 return &responseObject, fmt.Errorf("while marshaling SetStatus request: %w", err) 133 } 134 absoluteURL := appendId(c.avsConfig.ApiEndpoint, evaluationID) 135 136 request, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/lifecycle", absoluteURL), bytes.NewReader(objAsBytes)) 137 if err != nil { 138 return &responseObject, fmt.Errorf("while creating SetStatus request: %w", err) 139 } 140 request.Header.Set("Content-Type", "application/json") 141 142 response, err := c.execute(request, true, true) 143 if err != nil { 144 return &responseObject, fmt.Errorf("while executing SetStatus request: %w", err) 145 } 146 defer func() { 147 if closeErr := c.closeResponseBody(response); closeErr != nil { 148 err = kebError.AsTemporaryError(closeErr, "while closing SetStatus response") 149 } 150 }() 151 152 err = json.NewDecoder(response.Body).Decode(&responseObject) 153 if err != nil { 154 return nil, fmt.Errorf("while decode SetStatus response: %w", err) 155 } 156 157 return &responseObject, nil 158 } 159 160 func (c *Client) RemoveReferenceFromParentEval(parentID, evaluationID int64) (err error) { 161 absoluteURL := fmt.Sprintf("%s/child/%d", appendId(c.avsConfig.ApiEndpoint, parentID), evaluationID) 162 response, err := c.deleteRequest(absoluteURL) 163 if err == nil { 164 return nil 165 } 166 167 if response != nil && response.StatusCode == http.StatusBadRequest { 168 defer func() { 169 if closeErr := c.closeResponseBody(response); closeErr != nil { 170 err = kebError.AsTemporaryError(closeErr, "while closing body") 171 } 172 }() 173 buff, err := io.ReadAll(response.Body) 174 if err != nil { 175 return fmt.Errorf("unable to read the response body: %w", err) 176 } 177 var responseObject avsApiErrorResp 178 err = json.NewDecoder(bytes.NewReader(buff)).Decode(&responseObject) 179 if err != nil { 180 return fmt.Errorf("while decoding AvS non success response body for ID: %d, URL: %s, error: %w", 181 evaluationID, absoluteURL, err) 182 } 183 if strings.Contains(strings.ToLower(responseObject.Message), "does not contain subevaluation") { 184 return nil 185 } 186 return fmt.Errorf("unable to delete subevaluation %d reference from the parent evaluation: %s", evaluationID, responseObject.Message) 187 } 188 return fmt.Errorf("unexpected response for evaluationId: %d while deleting reference from parent evaluation, error: %w", evaluationID, err) 189 } 190 191 func (c *Client) DeleteEvaluation(evaluationId int64) (err error) { 192 absoluteURL := appendId(c.avsConfig.ApiEndpoint, evaluationId) 193 response, err := c.deleteRequest(absoluteURL) 194 defer func() { 195 if closeErr := c.closeResponseBody(response); closeErr != nil { 196 err = kebError.AsTemporaryError(closeErr, "while closing DeleteEvaluation response body") 197 } 198 }() 199 if err != nil { 200 return fmt.Errorf("while deleting evaluation: %w", err) 201 } 202 203 return nil 204 } 205 206 func appendId(baseUrl string, id int64) string { 207 if strings.HasSuffix(baseUrl, "/") { 208 return baseUrl + strconv.FormatInt(id, 10) 209 } else { 210 return baseUrl + "/" + strconv.FormatInt(id, 10) 211 } 212 } 213 214 func (c *Client) deleteRequest(absoluteURL string) (*http.Response, error) { 215 req, err := http.NewRequest(http.MethodDelete, absoluteURL, nil) 216 if err != nil { 217 return nil, fmt.Errorf("while creating delete request: %w", err) 218 } 219 220 response, err := c.execute(req, true, true) 221 if err != nil { 222 return response, fmt.Errorf("while executing delete request for path: %s: %w", absoluteURL, err) 223 } 224 225 return response, nil 226 } 227 228 func (c *Client) execute(request *http.Request, allowNotFound bool, allowResetToken bool) (*http.Response, error) { 229 httpClient, err := getHttpClient(c.ctx, c.avsConfig) 230 if err != nil { 231 return &http.Response{}, fmt.Errorf("while getting http client: %w", err) 232 } 233 defer httpClient.CloseIdleConnections() 234 response, err := httpClient.Do(request) 235 if err != nil { 236 return &http.Response{}, kebError.AsTemporaryError(err, "while executing request by http client") 237 } 238 239 if response.StatusCode >= http.StatusInternalServerError { 240 return response, kebError.WrapNewTemporaryError(NewAvsError("avs server returned %d status code", response.StatusCode)) 241 } 242 243 switch response.StatusCode { 244 case http.StatusOK, http.StatusCreated: 245 return response, nil 246 case http.StatusNotFound: 247 if allowNotFound { 248 return response, nil 249 } 250 return response, NewAvsError("response status code: %d for %s", http.StatusNotFound, request.URL.String()) 251 case http.StatusUnauthorized: 252 if allowResetToken { 253 return c.execute(request, allowNotFound, false) 254 } 255 return response, NewAvsError("avs server returned %d status code twice for %s", http.StatusUnauthorized, request.URL.String()) 256 } 257 258 return response, NewAvsError("unsupported status code: %d for %s.", response.StatusCode, request.URL.String()) 259 } 260 261 func (c *Client) closeResponseBody(response *http.Response) error { 262 if response == nil { 263 return nil 264 } 265 if response.Body == nil { 266 return nil 267 } 268 // drain the body to let the transport reuse the connection 269 io.Copy(io.Discard, response.Body) 270 271 return response.Body.Close() 272 } 273 274 func responseBody(resp *http.Response) string { 275 bodyBytes, err := ioutil.ReadAll(resp.Body) 276 if err != nil { 277 return "" 278 } 279 return string(bodyBytes) 280 } 281 282 func getHttpClient(ctx context.Context, cfg Config) (http.Client, error) { 283 config := oauth2.Config{ 284 ClientID: cfg.OauthClientId, 285 Endpoint: oauth2.Endpoint{ 286 TokenURL: cfg.OauthTokenEndpoint, 287 AuthStyle: oauth2.AuthStyleInHeader, 288 }, 289 } 290 291 initialToken, err := config.PasswordCredentialsToken(ctx, cfg.OauthUsername, cfg.OauthPassword) 292 if err != nil { 293 return http.Client{}, kebError.AsTemporaryError(err, "while fetching initial token") 294 } 295 296 return *config.Client(ctx, initialToken), nil 297 }