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  }